API Gateway
AWS API Gateway for building, deploying, and managing RESTful and WebSocket APIs
You are an expert in Amazon API Gateway for creating, publishing, and managing REST APIs, HTTP APIs, and WebSocket APIs at any scale. ## Key Points - **Embedding secrets in Lambda authorizer responses without encryption** -- The authorizer context is visible in CloudWatch logs. Never pass sensitive tokens or PII through the policy context. - **Not setting up a custom domain** -- Exposing raw API Gateway URLs (e.g., `abc123.execute-api.region.amazonaws.com`) to clients makes migration and versioning painful later. - **Use HTTP API over REST API** for Lambda integrations unless you need REST-specific features (request transformation, caching, WAF, usage plans). - **Enable request validation** on REST APIs to reject malformed requests before they hit Lambda, saving cost and improving security. - **Use stage variables** to deploy the same API definition across environments (dev, staging, prod) pointing to different Lambda aliases. - **Set up throttling** at the API, stage, and route level to protect backend services from traffic spikes. - **Enable CloudWatch access logging** for debugging and audit. Use structured JSON log format. - **Use Lambda authorizer result caching** (TTL) for REST APIs to avoid invoking the authorizer on every request. - **Set up a custom domain** with ACM certificate for production APIs. Use base path mappings for API versioning. - **Enable X-Ray tracing** for end-to-end request visibility across API Gateway and Lambda. - **Missing Lambda invoke permission**: API Gateway needs `lambda:InvokeFunction` permission on the Lambda function. Without it, you get 500 errors. - **CORS errors**: For REST APIs, you must configure CORS on the OPTIONS method AND return CORS headers from Lambda. HTTP APIs handle this automatically with `CorsConfiguration`. ## Quick Example ```bash aws apigateway import-rest-api --body fileb://openapi.yaml ```
skilldb get aws-services-skills/API GatewayFull skill: 298 linesAWS API Gateway — Cloud Services
You are an expert in Amazon API Gateway for creating, publishing, and managing REST APIs, HTTP APIs, and WebSocket APIs at any scale.
Core Philosophy
API Gateway is the front door to your backend. Every request from the outside world passes through it, so it must be both secure by default and operationally transparent. Choose the simplest API type that meets your requirements: HTTP APIs for the vast majority of Lambda-backed services, REST APIs only when you need request transformation, caching, or WAF integration. Starting with HTTP API and upgrading later is cheaper and faster than over-engineering with REST API upfront.
Treat your API definition as a contract. Use OpenAPI specifications to define routes, request schemas, and response models declaratively. This makes your API self-documenting, enables request validation at the gateway layer (rejecting bad input before it reaches your Lambda), and lets you generate client SDKs automatically. Version your APIs via custom domains and base path mappings rather than embedding version numbers in route paths.
Security is not optional configuration. Every production API should have an authorizer (JWT, Lambda, or IAM), throttling limits at both the account and route level, and CloudWatch access logging enabled. API Gateway is also the right place to enforce CORS rather than scattering CORS headers across individual Lambda functions.
Anti-Patterns
- Defaulting to REST API for every project -- REST API is heavier and more expensive. Unless you need caching, WAF, request transformation, or usage plans, HTTP API is the correct choice for Lambda integrations.
- Skipping request validation -- Letting malformed requests pass through to Lambda wastes compute, increases latency, and widens your attack surface. Validate request bodies and query parameters at the gateway.
- Embedding secrets in Lambda authorizer responses without encryption -- The authorizer context is visible in CloudWatch logs. Never pass sensitive tokens or PII through the policy context.
- Ignoring the 29-second hard timeout -- Designing synchronous APIs for long-running operations leads to gateway timeouts. Use asynchronous patterns (202 + polling, or WebSocket push) for anything that might exceed 15 seconds.
- Not setting up a custom domain -- Exposing raw API Gateway URLs (e.g.,
abc123.execute-api.region.amazonaws.com) to clients makes migration and versioning painful later.
Overview
API Gateway provides three API types: REST API (full-featured, request/response transformation, caching, WAF), HTTP API (lower latency, lower cost, simpler), and WebSocket API (real-time bidirectional communication). It integrates with Lambda, HTTP backends, and AWS services. API Gateway handles throttling, authentication, CORS, request validation, and usage plans with API keys.
Setup & Configuration
Create an HTTP API (Recommended for Lambda)
# Create HTTP API with Lambda integration
aws apigatewayv2 create-api \
--name my-app-api \
--protocol-type HTTP \
--cors-configuration '{
"AllowOrigins": ["https://myapp.com"],
"AllowMethods": ["GET", "POST", "PUT", "DELETE"],
"AllowHeaders": ["Authorization", "Content-Type"],
"MaxAge": 86400
}'
# Create Lambda integration
aws apigatewayv2 create-integration \
--api-id abc123 \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:us-east-1:123456789012:function:my-handler \
--payload-format-version 2.0
# Create route
aws apigatewayv2 create-route \
--api-id abc123 \
--route-key "GET /items" \
--target integrations/def456
# Deploy
aws apigatewayv2 create-stage \
--api-id abc123 \
--stage-name prod \
--auto-deploy
REST API with OpenAPI
# openapi.yaml
openapi: "3.0.1"
info:
title: My App API
version: "1.0"
paths:
/items:
get:
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: POST
uri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:list-items/invocations
responses:
"200":
description: Success
post:
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: POST
uri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:create-item/invocations
x-amazon-apigateway-request-validator: all
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateItemRequest"
components:
schemas:
CreateItemRequest:
type: object
required: [name]
properties:
name:
type: string
minLength: 1
description:
type: string
aws apigateway import-rest-api --body fileb://openapi.yaml
Core Patterns
Lambda Handler for HTTP API (v2 Payload)
import json
def handler(event, context):
method = event["requestContext"]["http"]["method"]
path = event["rawPath"]
query = event.get("queryStringParameters") or {}
body = json.loads(event.get("body", "{}")) if event.get("body") else {}
user_id = event["requestContext"].get("authorizer", {}).get("jwt", {}).get("claims", {}).get("sub")
if method == "GET" and path == "/items":
items = list_items(query.get("cursor"), int(query.get("limit", "20")))
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(items),
}
if method == "POST" and path == "/items":
item = create_item(body, user_id)
return {"statusCode": 201, "body": json.dumps(item)}
return {"statusCode": 404, "body": json.dumps({"error": "Not found"})}
JWT Authorizer (HTTP API)
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--name cognito-jwt \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--jwt-configuration '{
"Audience": ["1a2b3c4d5e6f7g8h9i0j"],
"Issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123"
}'
# Attach to route
aws apigatewayv2 update-route \
--api-id abc123 \
--route-id xyz789 \
--authorizer-id auth001 \
--authorization-type JWT
Lambda Authorizer (Custom Auth Logic)
# Lambda authorizer for REST API (token-based)
def handler(event, context):
token = event["authorizationToken"].replace("Bearer ", "")
method_arn = event["methodArn"]
try:
claims = verify_token(token)
return generate_policy(claims["sub"], "Allow", method_arn, claims)
except Exception:
return generate_policy("anonymous", "Deny", method_arn)
def generate_policy(principal_id, effect, resource, context=None):
policy = {
"principalId": principal_id,
"policyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "execute-api:Invoke",
"Effect": effect,
"Resource": resource,
}],
},
}
if context:
# Pass claims to downstream Lambda via requestContext.authorizer
policy["context"] = {
"user_id": context.get("sub", ""),
"email": context.get("email", ""),
}
return policy
Usage Plans and API Keys (REST API)
# Create usage plan
aws apigateway create-usage-plan \
--name premium-plan \
--throttle burstLimit=100,rateLimit=50 \
--quota limit=10000,period=MONTH \
--api-stages apiId=abc123,stage=prod
# Create API key
aws apigateway create-api-key --name partner-key --enabled
aws apigateway create-usage-plan-key \
--usage-plan-id plan001 \
--key-id key001 \
--key-type API_KEY
Custom Domain Name
# Create custom domain
aws apigatewayv2 create-domain-name \
--domain-name api.myapp.com \
--domain-name-configurations CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc-123
# Map to API stage
aws apigatewayv2 create-api-mapping \
--domain-name api.myapp.com \
--api-id abc123 \
--stage prod \
--api-mapping-key v1 # Results in api.myapp.com/v1/...
CloudFormation: HTTP API with Lambda
Resources:
HttpApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: my-api
ProtocolType: HTTP
CorsConfiguration:
AllowOrigins: ["https://myapp.com"]
AllowMethods: ["GET", "POST"]
LambdaIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref HttpApi
IntegrationType: AWS_PROXY
IntegrationUri: !GetAtt HandlerFunction.Arn
PayloadFormatVersion: "2.0"
GetItemsRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref HttpApi
RouteKey: GET /items
Target: !Sub "integrations/${LambdaIntegration}"
DefaultStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref HttpApi
StageName: $default
AutoDeploy: true
LambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref HandlerFunction
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*"
Best Practices
- Use HTTP API over REST API for Lambda integrations unless you need REST-specific features (request transformation, caching, WAF, usage plans).
- Enable request validation on REST APIs to reject malformed requests before they hit Lambda, saving cost and improving security.
- Use stage variables to deploy the same API definition across environments (dev, staging, prod) pointing to different Lambda aliases.
- Set up throttling at the API, stage, and route level to protect backend services from traffic spikes.
- Enable CloudWatch access logging for debugging and audit. Use structured JSON log format.
- Use Lambda authorizer result caching (TTL) for REST APIs to avoid invoking the authorizer on every request.
- Set up a custom domain with ACM certificate for production APIs. Use base path mappings for API versioning.
- Enable X-Ray tracing for end-to-end request visibility across API Gateway and Lambda.
Common Pitfalls
- Missing Lambda invoke permission: API Gateway needs
lambda:InvokeFunctionpermission on the Lambda function. Without it, you get 500 errors. - CORS errors: For REST APIs, you must configure CORS on the OPTIONS method AND return CORS headers from Lambda. HTTP APIs handle this automatically with
CorsConfiguration. - 29-second timeout: API Gateway has a hard 29-second integration timeout. For long-running operations, use async patterns (return 202, process via SQS/Step Functions).
- Payload size limits: 10 MB for REST API, 10 MB for HTTP API request/response payloads. Use S3 presigned URLs for large file uploads.
- Binary content handling: REST API requires binary media type configuration. HTTP API handles binary natively.
- Stage deployment confusion (REST API): Changes to REST APIs are not live until you create a deployment. Use
aws apigateway create-deploymentafter changes. HTTP APIs withAutoDeployavoid this. - Throttling 429 errors: Default account-level limit is 10,000 RPS across all APIs in a region. Request increases for high-traffic APIs.
- CloudWatch log role missing: REST API access logging requires a CloudWatch role ARN set at the account level via
aws apigateway update-account --patch-operations op=replace,path=/cloudwatchRoleArn,value=<arn>.
Install this skill directly: skilldb add aws-services-skills
Related Skills
Cloudformation
AWS CloudFormation infrastructure-as-code for provisioning and managing AWS resources declaratively
Cognito
AWS Cognito user authentication and authorization for web and mobile applications
Dynamodb
AWS DynamoDB NoSQL database for high-performance key-value and document workloads
Ecs Fargate
AWS ECS and Fargate for running containerized applications without managing servers
Rds Aurora
AWS RDS and Aurora managed relational databases for production SQL workloads
S3
AWS S3 object storage service for scalable, durable file and data storage