AWS API Gateway: REST and HTTP APIs Complete Guide (2026)
AWS API Gateway is a fully managed service for creating, publishing, and securing APIs at any scale. It handles traffic management, authorization, throttling, monitoring, and versioning so your backend code doesn't have to. There are three flavors: REST APIs (the original, feature-rich option), HTTP APIs (newer, faster, cheaper), and WebSocket APIs (for real-time bidirectional communication). Choosing the right one — and configuring it correctly — makes a significant difference in cost, latency, and maintainability.
Table of Contents
REST API vs HTTP API vs WebSocket API
Pick the wrong API type and you'll either overpay or hit feature gaps. Here's the decision matrix:
| Feature | REST API | HTTP API | WebSocket API |
|---|---|---|---|
| Latency | ~6ms overhead | ~1ms overhead | Persistent |
| Price per million calls | $3.50 | $1.00 | $1.00 + connection fees | Request/response transforms | Yes (VTL templates) | No | No |
| Usage plans / API keys | Yes | No (use JWT claims) | No |
| Edge-optimized (CloudFront) | Yes | No | No |
| WAF integration | Yes | Yes | No |
| Private integrations (VPC Link) | Yes (NLB) | Yes (ALB/NLB/Cloud Map) | No |
| Best for | Complex API management, B2B APIs with API keys | Simple Lambda-backed APIs, microservices | Chat, live dashboards, gaming |
Lambda Proxy Integration
Lambda proxy integration is the simplest way to connect API Gateway to Lambda. The entire HTTP request (headers, query params, body, path variables) is passed to Lambda as a structured JSON event. Your Lambda returns a JSON object with statusCode, headers, and body.
# Lambda handler for a REST API proxy integration (Python)
import json
def handler(event, context):
# event contains: httpMethod, path, pathParameters,
# queryStringParameters, headers, body, requestContext
method = event['httpMethod']
path = event['path']
user_id = event.get('pathParameters', {}).get('userId')
query = event.get('queryStringParameters') or {}
if method == 'GET' and user_id:
user = get_user_from_db(user_id)
if not user:
return {
'statusCode': 404,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'User not found'})
}
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=60'
},
'body': json.dumps(user)
}
return {
'statusCode': 405,
'body': json.dumps({'error': 'Method not allowed'})
}
For HTTP APIs, the event format is slightly different (v2 payload format). The body is a plain string, and the method/path come from event['requestContext']['http'].
# HTTP API v2 payload format
def handler(event, context):
http_context = event['requestContext']['http']
method = http_context['method']
path = http_context['path']
body = json.loads(event.get('body', '{}'))
return {
'statusCode': 200,
'body': json.dumps({'message': 'OK'})
# No need to set Content-Type — API Gateway infers it
}
Request/Response Mapping Templates
REST API mapping templates use Velocity Template Language (VTL) to transform requests before they hit the backend and transform responses before returning to the client. This lets you adapt a legacy backend to a modern API contract without changing the backend code.
## Request mapping template: extract JSON body fields and pass as query string
## (useful for SQS SendMessage integration)
Action=SendMessage
&QueueUrl=$util.urlEncode("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue")
&MessageBody=$util.urlEncode($input.body)
&MessageAttribute.1.Name=eventType
&MessageAttribute.1.Value.DataType=String
&MessageAttribute.1.Value.StringValue=$util.urlEncode($input.path('$.eventType'))
## Response mapping template: reshape the backend response
#set($inputRoot = $input.path('$'))
{
"id": "$inputRoot.SendMessageResponse.SendMessageResult.MessageId",
"status": "queued"
}
This pattern — API Gateway → SQS (no Lambda) — is a cost-effective way to ingest webhooks. You pay only for API Gateway and SQS calls, no Lambda cold starts on the ingestion path.
Authorizers: Lambda and Cognito
API Gateway supports three authorization mechanisms: IAM (for AWS service-to-service), Cognito User Pools (for user authentication), and Lambda authorizers (custom logic).
Cognito authorizer — validates a JWT from Cognito User Pools:
# Attach a Cognito authorizer to an HTTP API
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--name CognitoAuth \
--jwt-configuration '{
"Audience": ["your-app-client-id"],
"Issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID"
}'
Lambda authorizer — for custom token validation (API keys, internal JWTs, OAuth introspection):
import json
def handler(event, context):
token = event.get('authorizationToken', '').replace('Bearer ', '')
# Validate the token against your auth service
principal_id = validate_token(token)
if not principal_id:
raise Exception('Unauthorized')
return {
'principalId': principal_id,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': 'Allow',
'Resource': event['methodArn']
}]
},
'context': {
'userId': principal_id,
'tier': 'premium' # passed to Lambda backend via $context.authorizer.tier
}
}
Throttling and Usage Plans
API Gateway throttles at two levels: account-level defaults (10,000 RPS burst, 5,000 RPS steady-state per region) and per-stage/method limits. Usage plans let you give different API key holders different rate limits.
# Create a usage plan with per-key throttling
aws apigateway create-usage-plan \
--name "Standard" \
--api-stages '[{"apiId":"abc123","stage":"prod"}]' \
--throttle '{"burstLimit":100,"rateLimit":50}' \
--quota '{"limit":10000,"period":"MONTH"}'
# Create an API key and associate with the plan
KEY_ID=$(aws apigateway create-api-key \
--name "partner-acme" --enabled \
--query 'id' --output text)
aws apigateway create-usage-plan-key \
--usage-plan-id plan123 \
--key-id $KEY_ID \
--key-type API_KEY
When a client exceeds their quota, they receive HTTP 429 Too Many Requests. Implement exponential backoff in your API clients to handle this gracefully.
Response Caching
REST APIs support response caching at the stage level (0.5 GB to 237 GB). Cached responses bypass your backend entirely, reducing latency and cost for read-heavy endpoints.
# Enable caching on a specific method
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations \
op=replace,path=/cacheClusterEnabled,value=true \
op=replace,path=/cacheClusterSize,value=0.5 \
op=replace,path=/~1users~1{userId}/GET/caching/enabled,value=true \
op=replace,path=/~1users~1{userId}/GET/caching/ttlInSeconds,value=60
CORS Configuration
CORS errors are the most common API Gateway gotcha for frontend developers. For HTTP APIs, CORS is configured at the API level. For REST APIs, you must configure CORS per-resource and ensure your Lambda returns the correct headers.
# HTTP API: Configure CORS at the API level (cleanest approach)
aws apigatewayv2 update-api \
--api-id abc123 \
--cors-configuration '{
"AllowOrigins": ["https://app.example.com"],
"AllowMethods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"AllowHeaders": ["Content-Type", "Authorization", "X-Api-Key"],
"ExposeHeaders": ["X-Request-Id"],
"MaxAge": 3600,
"AllowCredentials": true
}'
For REST APIs, also ensure your Lambda returns CORS headers in every response (not just OPTIONS), and that the OPTIONS method has a mock integration returning 200 with the appropriate headers.
Custom Domain Names
By default your API is at https://abc123.execute-api.us-east-1.amazonaws.com/prod. Custom domains map it to https://api.example.com.
# Prerequisite: ACM certificate in us-east-1 (for edge-optimized) or same region (for regional)
CERT_ARN="arn:aws:acm:us-east-1:123456789012:certificate/abc"
# Create a custom domain
aws apigateway create-domain-name \
--domain-name api.example.com \
--regional-certificate-arn $CERT_ARN \
--endpoint-configuration types=REGIONAL
# Map the domain to a stage
aws apigateway create-base-path-mapping \
--domain-name api.example.com \
--rest-api-id abc123 \
--stage prod \
--base-path ""
# Get the regional domain name to create a Route 53 ALIAS record
aws apigateway get-domain-name \
--domain-name api.example.com \
--query 'regionalDomainName'
Stages and Deployments
REST APIs require explicit deployments — changes to your API definition don't go live until you deploy them to a stage. HTTP APIs auto-deploy by default (configurable).
# Deploy a REST API to the prod stage
aws apigateway create-deployment \
--rest-api-id abc123 \
--stage-name prod \
--description "Release 2.3.0 — add user preferences endpoint"
# Canary deployment: send 10% of traffic to new deployment
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations \
op=replace,path=/canarySettings/percentTraffic,value=10 \
op=replace,path=/canarySettings/deploymentId,value=NEW_DEPLOY_ID
# After validation, promote canary to 100%
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations op=replace,path=/deploymentId,value=NEW_DEPLOY_ID
Frequently Asked Questions
10 MB for REST APIs and HTTP APIs. For file uploads larger than 10 MB, generate a presigned S3 URL in your Lambda and have the client upload directly to S3. This bypasses API Gateway entirely for the binary upload.
29 seconds for REST and HTTP APIs (this is a hard limit, not configurable). If your Lambda takes longer than 29 seconds, the client gets a 504 Gateway Timeout. For long-running jobs, use an async pattern: API Gateway → SQS → Lambda, and poll for results separately.
Use a VPC Link. For HTTP APIs, create a VPC Link that points to an ALB, NLB, or Cloud Map service inside your VPC. API Gateway routes requests through the VPC Link without traversing the public internet. See the ECS guide for ALB setup.
A 502 from API Gateway means the backend returned a malformed response. For Lambda integrations, this usually means your function threw an unhandled exception or returned an object that doesn't match the expected format (missing statusCode). Enable CloudWatch execution logs on the stage at INFO level — API Gateway logs the exact error from the integration response.
ALB is cheaper (no per-request charge beyond the LCU model) and has lower latency for high-throughput services. Use API Gateway when you need: auth (JWT/Lambda authorizers), throttling, usage plans, request transformation, or a managed API catalog. For internal microservices, ALB is usually the right choice.