DEV Community

Cover image for Amazon API Gateway: REST vs HTTP APIs, Explained via the AWS CLI
Bry
Bry

Posted on • Originally published at Medium

Amazon API Gateway: REST vs HTTP APIs, Explained via the AWS CLI

Key Points

  • REST API and HTTP API are two separate products under one brand name, with two separate CLI command namespaces — aws apigateway and aws apigatewayv2. They are not versions of each other.
  • HTTP API costs $1.00 per million calls versus REST API's flat $3.50 per million — a 71% difference that compounds fast at scale.
  • Pick REST API only when you need something HTTP API structurally cannot do: API keys, per-client usage plans, AWS WAF, request validation, or private (VPC-only) endpoints.
  • The CLI reveals the real gap between them better than any feature table: building the same Lambda-backed endpoint takes one command on HTTP API and five on REST API.
  • Tear down what you build. Both products bill for idle resources, and orphaned stages are the most common line item nobody remembers creating.

Prerequisites

  • CLI/SDK version tested against: aws-cli/2.35.x (current stable as of this writing — the apigatewayv2 and apigateway command sets are stable across 2.x minor releases, but always run aws --version before following along)
  • An AWS account with an IAM user or role that has apigateway:* and lambda:* permissions, and a default region configured (aws configure)
  • A deployed Lambda function to integrate against — the examples below use a function named user-lookup

Introduction

Every AWS API Gateway tutorial I've read picks a product on page one and never explains why. Half of them build a REST API with create-resource and put-method chains that look like plumbing. The other half quick-create an HTTP API in one line and call it a day. Neither tells you when the other approach would have been the better call — and I've watched teams build a REST API out of habit, hit the $3.50-per-million bill at scale, and only then discover HTTP API existed.

The two products share a brand name and almost nothing else under the hood. They have separate CLI command namespaces, separate feature sets, and a 3.5x price difference that AWS designed on purpose, not as an afterthought. Understanding which one you're actually building — and why — matters more than memorizing either command sequence.

This article builds the identical Lambda-backed endpoint twice: once on HTTP API, once on REST API, entirely from the AWS CLI. You'll see where the commands diverge, where they can't diverge because one product simply lacks the feature, and which one to default to for your next project.


Two Products, Not Two Versions

AWS API Gateway is a brand covering three products: REST API, HTTP API, and WebSocket API. REST API launched in 2015. HTTP API launched in 2019 as a deliberately smaller product — AWS's own announcement framed it as coverage for the roughly 80% of REST API customers who only needed Lambda proxy integration, JWT auth, and CORS, and were paying REST API prices for features they never touched.

Two Products, Not Two Versions

Diagram: One brand, two CLI namespaces, non-overlapping feature sets.

That last point trips people up the most: aws apigateway and aws apigatewayv2 are not "v1 and v2 of the same thing" the way S3's SDK versions are. They are two independent command trees. A script written for REST API will not run against HTTP API with a find-and-replace.


Building the Same Endpoint on HTTP API

Start with HTTP API, because it's shorter and shows what "quick create" actually collapses into one call.

# Quick-create an HTTP API backed directly by a Lambda function.
# This single command creates the API, a default catch-all route,
# a Lambda integration, and an auto-deploying default stage.
aws apigatewayv2 create-api \
  --name user-lookup-http \
  --protocol-type HTTP \
  --target "arn:aws:lambda:us-east-1:123456789012:function:user-lookup"
Enter fullscreen mode Exit fullscreen mode

That one call is doing the work of five REST API calls, which is the point. Quick create only works for a single catch-all route. The moment you need more than one route with different backends, you build it up explicitly:

# Explicit build — needed once you have more than one route.
API_ID=$(aws apigatewayv2 create-api \
  --name user-lookup-http \
  --protocol-type HTTP \
  --query 'ApiId' --output text)

INTEGRATION_ID=$(aws apigatewayv2 create-integration \
  --api-id "$API_ID" \
  --integration-type AWS_PROXY \
  --integration-uri "arn:aws:lambda:us-east-1:123456789012:function:user-lookup" \
  --payload-format-version "2.0" \
  --query 'IntegrationId' --output text)

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key "GET /users/{userId}" \
  --target "integrations/$INTEGRATION_ID"

# HTTP API stages auto-deploy by default — no separate deployment call needed
aws apigatewayv2 create-stage \
  --api-id "$API_ID" \
  --stage-name '$default' \
  --auto-deploy
Enter fullscreen mode Exit fullscreen mode

Four commands, no separate deployment step because $default auto-deploys on every change. Add a JWT authorizer — the feature HTTP API supports natively and REST API cannot without a Lambda authorizer standing in for it:

aws apigatewayv2 create-authorizer \
  --api-id "$API_ID" \
  --authorizer-type JWT \
  --identity-source '$request.header.Authorization' \
  --name user-pool-jwt \
  --jwt-configuration '{"Audience":["https://api.example.com"],"Issuer":"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX"}'
Enter fullscreen mode Exit fullscreen mode

Grant API Gateway permission to invoke the Lambda, and the endpoint is live:

aws lambda add-permission \
  --function-name user-lookup \
  --statement-id apigw-http-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:${API_ID}/*/*/users/*"

curl "https://${API_ID}.execute-api.us-east-1.amazonaws.com/users/usr_42"
Enter fullscreen mode Exit fullscreen mode

Total: five commands from zero to a JWT-protected, auto-deploying endpoint.


Building the Same Endpoint on REST API

Now the same endpoint on REST API. Structurally, REST API models resources as a tree — you create the API, then walk down from the root resource creating child resources, then attach an HTTP method and integration to each resource individually.

Building the Same Endpoint on REST API

Diagram: REST API's resource-tree model requires an explicit deployment step that HTTP API's auto-deploy stages skip entirely.

API_ID=$(aws apigateway create-rest-api \
  --name user-lookup-rest \
  --endpoint-configuration types=REGIONAL \
  --query 'id' --output text)

ROOT_ID=$(aws apigateway get-resources \
  --rest-api-id "$API_ID" \
  --query 'items[?path==`/`].id' --output text)

USERS_ID=$(aws apigateway create-resource \
  --rest-api-id "$API_ID" \
  --parent-id "$ROOT_ID" \
  --path-part users \
  --query 'id' --output text)

USERID_RESOURCE=$(aws apigateway create-resource \
  --rest-api-id "$API_ID" \
  --parent-id "$USERS_ID" \
  --path-part '{userId}' \
  --query 'id' --output text)

aws apigateway put-method \
  --rest-api-id "$API_ID" \
  --resource-id "$USERID_RESOURCE" \
  --http-method GET \
  --authorization-type NONE

aws apigateway put-integration \
  --rest-api-id "$API_ID" \
  --resource-id "$USERID_RESOURCE" \
  --http-method GET \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:user-lookup/invocations"

# REST API has no auto-deploy — every change requires an explicit deployment
aws apigateway create-deployment \
  --rest-api-id "$API_ID" \
  --stage-name prod
Enter fullscreen mode Exit fullscreen mode

Seven commands versus HTTP API's five, and that's before adding auth — REST API has no native JWT authorizer at all. You'd add a Lambda authorizer via create-authorizer --type TOKEN, which is its own Lambda function to write, deploy, and maintain, purely to validate a token that HTTP API checks natively with one --jwt-configuration flag. AUTHORIZATION_TYPE=NONE above is intentionally the simplest case to keep the comparison fair — swapping in Cognito or a Lambda authorizer adds two more commands on the REST API side and zero on the HTTP API side.

That gap is the entire argument for defaulting to HTTP API. Not the price alone — the operational surface. Every additional resource, method, and integration on REST API is something you version, test, and can misconfigure. HTTP API's route-based model has fewer places to get it wrong.


The Feature Gap That Actually Justifies REST API

None of this means REST API is obsolete. It means most projects don't need what it offers. Per AWS's own comparison documentation, REST API supports several things HTTP API structurally cannot do at all:

Feature REST API HTTP API
API keys + usage plans Yes No
Per-client rate limiting Yes No
AWS WAF integration Yes No
Request validation (schema) Yes No
Private (VPC-only) endpoints Yes No
Response caching Yes No
Mock integrations Yes No
JWT authorizer (native) No Yes
Auto-deploying stages No Yes
Private integrations via AWS Cloud Map No Yes

If you're selling API access to third-party developers and need to issue API keys with per-key quotas, that's REST API, full stop — HTTP API has no equivalent mechanism, not a smaller version of one. Same for WAF: if compliance requires a web application firewall in front of the gateway, REST API is your only option between the two. I've built exactly one REST API in the last two years for a client billing external partners per API key against a usage plan — every other new project defaulted to HTTP API.


Cost at Scale

The sticker prices understate how fast this compounds. At 500 million requests a month:

HTTP API REST API
First 300M $300 (at $1.00/M) $1,050 (at $3.50/M)
Remaining 200M $180 (at $0.90/M) $700
Monthly total $480 $1,750

That's before data transfer, which is identical on both ($0.09/GB after the first 100 GB). A team that defaults to REST API out of habit on a 500M-request workload is paying an extra $1,270 a month for features it may never touch. Multiply that by however many services in your org made the same default choice and it stops being a rounding error.


Common Mistakes

Mistake 1: Choosing REST API because older tutorials only cover it
Most CLI tutorials predate HTTP API's 2019 launch or were never updated. If a walkthrough uses aws apigateway create-rest-api and never mentions apigatewayv2, it's not wrong, just incomplete — check whether you actually need REST API's feature set before copying it.

Mistake 2: Assuming apigatewayv2 supports API keys
It doesn't, and there's no workaround at the gateway layer. Teams that need lightweight per-client identification on HTTP API end up implementing it themselves — a custom header checked in the Lambda, or a JWT claim — rather than getting it for free the way REST API's usage plans provide it.

Mistake 3: Forgetting the deployment step on REST API
Unlike HTTP API's auto-deploying $default stage, REST API changes are invisible until you run create-deployment. I've debugged more than one "my change isn't showing up" ticket that was just a missing deployment call after a put-integration update.

Mistake 4: Leaving orphaned APIs after testing
Both products bill nothing for an idle API with zero traffic, but Lambda add-permission grants and unused custom domain mappings pile up and become a mess to audit six months later. Clean up as you go.


Teardown

Both products are cheap to tear down completely, and you should — leftover test APIs are how AWS bills quietly creep up.

# HTTP API
aws apigatewayv2 delete-api --api-id "$API_ID"

# REST API
aws apigateway delete-rest-api --rest-api-id "$API_ID"
Enter fullscreen mode Exit fullscreen mode

Full teardown script, including the Lambda permission cleanup, is in the companion repo below.


Production Considerations

Performance: HTTP API adds roughly 6 ms of median gateway overhead; REST API adds roughly 11 ms. At p99, the gap narrows because both are dominated by backend latency, not gateway overhead — don't over-index on this number when choosing.

Security: If you're on HTTP API and need WAF-equivalent protection, put CloudFront with AWS WAF in front of it — HTTP API supports CloudFront origins natively. This is the standard workaround and it works well, but it's an extra piece of infrastructure REST API gives you natively.

Cost: Re-run the math above whenever traffic grows 5-10x. A choice that made sense at 10M requests a month can look very different at 500M.

Monitoring: Both emit CloudWatch metrics. Only REST API emits execution logs and X-Ray traces natively — on HTTP API, instrument tracing inside the Lambda itself if you need request-level tracing.


Full Example: Build-and-Verify Script

The companion repository includes a complete bash script that builds both APIs, verifies each with a live curl, and tears both down. The core verification logic:

#!/usr/bin/env bash
set -euo pipefail

verify_endpoint() {
  local url="$1"
  local label="$2"
  local status
  status=$(curl -s -o /dev/null -w '%{http_code}' "$url")
  if [[ "$status" == "200" ]]; then
    echo "OK   $label -> $status"
  else
    echo "FAIL $label -> $status"
    exit 1
  fi
}

verify_endpoint "https://${HTTP_API_ID}.execute-api.${AWS_REGION}.amazonaws.com/users/usr_42" "HTTP API"
verify_endpoint "https://${REST_API_ID}.execute-api.${AWS_REGION}.amazonaws.com/prod/users/usr_42" "REST API"
Enter fullscreen mode Exit fullscreen mode

Full source with setup, teardown, and verification scripts: GitHubcloud-apis/amazon-api-gateway-cli/


Conclusion

Default to HTTP API. It's cheaper, has fewer commands to build and maintain, and natively supports JWT auth — the thing most new services actually need. Reach for REST API only when you specifically need API keys with usage plans, WAF, request validation, caching, or private VPC-only endpoints — features HTTP API doesn't have a smaller version of, because it was never built to. The CLI makes this concrete in a way the marketing pages don't: five commands versus seven isn't just fewer keystrokes, it's fewer places for a misconfigured resource or a forgotten deployment to bite you in production.


Further Reading


If this helped, a like and a follow are appreciated — and if you've solved this differently, drop a comment, I'd like to hear it.

Bry Writes Code — cloud and API infrastructure specialist. Migrating a REST API to HTTP API, or deciding which one to build next? Get in touch.

Top comments (0)