Skip to main content
Technology & EngineeringAws Services298 lines

API Gateway

AWS API Gateway for building, deploying, and managing RESTful and WebSocket APIs

Quick Summary24 lines
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 lines
Paste into your CLAUDE.md or agent config

AWS 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: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.
  • 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-deployment after changes. HTTP APIs with AutoDeploy avoid 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

Get CLI access →