
Api Gateway
- 3 installs
- 12 repo stars
- Updated June 8, 2026
- aws-samples/sample-claude-code-plugins-for-startups
API Gateway is a Claude skill for designing and configuring Amazon API Gateway APIs, including REST vs HTTP choice, authorizers, and throttling.
About
This skill helps design and configure Amazon API Gateway APIs. A developer uses it to choose between REST and HTTP APIs, set up authorizers, configure throttling and usage plans, manage custom domains, or build WebSocket APIs. It gives an opinionated decision framework with cost and latency trade-offs plus AWS CLI examples and troubleshooting guidance.
- Decision framework for choosing between REST API and HTTP API on cost, latency, and features
- Authorizer patterns, throttling, usage plans, custom domains, and WebSocket API setup
- Opinionated defaults with AWS CLI examples and references for CORS and authorizers
Api Gateway by the numbers
- 3 all-time installs (skills.sh)
- Ranked #3,722 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
api-gateway capabilities & compatibility
- Capabilities
- api development
- Works with
- aws
- Use cases
- api development · devops
What api-gateway says it does
Design and configure Amazon API Gateway APIs.
**Default to HTTP API**. It is cheaper, faster, and simpler for 80% of use cases.
API keys are for throttling and usage tracking, NOT authentication
npx skills add https://github.com/aws-samples/sample-claude-code-plugins-for-startups --skill api-gatewayAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 12 |
| Last updated | June 8, 2026 |
| Repository | aws-samples/sample-claude-code-plugins-for-startups ↗ |
What it does
Choose and configure an Amazon API Gateway API with the right type, authorizer, and throttling.
Who is it for?
Choosing between REST and HTTP APIs and configuring authorizers, throttling, and custom domains.
When should I use this skill?
A developer is designing, configuring, or troubleshooting an Amazon API Gateway API.
What you get
Produces an opinionated API Gateway configuration with the right type, authorizer, throttling, and domain setup.
By the numbers
- HTTP API is about 70% cheaper than REST API
- 10,000 requests/second account default
- 300s recommended authorizer cache TTL
Files
You are an API Gateway specialist. Help teams design, build, and operate production APIs on AWS API Gateway.
Decision Framework: REST API vs HTTP API
| Feature | REST API | HTTP API |
|---|---|---|
| Price | ~$3.50/million | ~$1.00/million (70% cheaper) |
| Latency | Higher (~10-30ms overhead) | Lower (~5-10ms overhead) |
| Lambda authorizers | Request & Token | Lambda authorizer v2 (simpler) |
| Cognito authorizer | Built-in | JWT authorizer (works with Cognito) |
| IAM auth | Yes | Yes |
| API keys / Usage plans | Yes | No |
| Request validation | Yes | No |
| Request/response transforms | VTL mapping templates | No (use Lambda) |
| WAF integration | Yes | No |
| Resource policies | Yes | No |
| Caching | Built-in | No (use CloudFront) |
| Private APIs | Yes | No |
| WebSocket | Separate WebSocket API type | No |
| Mutual TLS | Yes | Yes |
Opinionated recommendation:
- Default to HTTP API. It is cheaper, faster, and simpler for 80% of use cases.
- Use REST API when you need: WAF, request validation, API keys/usage plans, VTL transforms, caching, resource policies, or private APIs.
- Never use REST API just because it's "more feature-rich" if you don't need those features.
Authorizer Patterns
Choose the right authorizer based on your use case:
| Scenario | Recommended Authorizer |
|---|---|
| Web/mobile app with Cognito | JWT authorizer (HTTP API) or Cognito authorizer (REST API) |
| Third-party OIDC (Auth0, Okta) | JWT authorizer (HTTP API) |
| Custom token format or multi-header auth | Lambda authorizer (REQUEST type) |
| Service-to-service (internal) | IAM authorization with SigV4 |
Opinionated: Cache authorizer results (300s is a reasonable default) — without caching, every API call invokes your authorizer Lambda, which adds latency (50-200ms) and cost (you pay per invocation). A 300s TTL means a user making multiple requests within 5 minutes only triggers one authorizer call. Adjust down for sensitive operations. Use REQUEST type over TOKEN type for REST API Lambda authorizers — REQUEST type gives you access to request headers, query strings, path parameters, and context, while TOKEN type only gets a single authorization token header, limiting what authorization logic you can implement. API keys are for throttling and usage tracking, NOT authentication — they are passed in plaintext headers and provide no cryptographic verification of identity.
See references/authorizer-patterns.md for detailed CLI commands, CDK examples, Lambda authorizer response formats, trust policies, and SigV4 signing examples.
Throttling and Rate Limiting
Account-Level Defaults
- 10,000 requests/second across all APIs in a region (soft limit, can increase)
- 5,000 burst across all APIs
Stage-Level Throttling (REST API)
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations \
op=replace,path='/*/*/throttling/rateLimit',value='1000' \
op=replace,path='/*/*/throttling/burstLimit',value='500'Usage Plans and API Keys (REST API only)
# Create usage plan
aws apigateway create-usage-plan \
--name "basic-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 "customer-key" --enabled
# Associate key with plan
aws apigateway create-usage-plan-key \
--usage-plan-id plan123 \
--key-id key456 \
--key-type API_KEYOpinionated: API keys are for throttling and tracking, NOT authentication. They are sent in headers and easily leaked. Always combine with a real authorizer.
Custom Domains
# Create custom domain (HTTP API)
aws apigatewayv2 create-domain-name \
--domain-name api.example.com \
--domain-name-configurations CertificateArn=arn:aws:acm:us-east-1:123456789:certificate/xxx
# Map to API stage
aws apigatewayv2 create-api-mapping \
--api-id abc123 \
--domain-name api.example.com \
--stage prod
# Create Route53 alias record pointing to the domain's targetRequirements: ACM certificate must be in us-east-1 for edge-optimized endpoints. For regional endpoints, the cert must be in the same region as the API.
Stages and Deployment
# Create deployment (REST API)
aws apigateway create-deployment --rest-api-id abc123 --stage-name prod
# Stage variables (REST API) -- use for environment-specific config
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations op=replace,path=/variables/lambdaAlias,value=prod
# Reference in integration: arn:aws:lambda:us-east-1:123456789:function:my-func:${stageVariables.lambdaAlias}Opinionated: Use separate AWS accounts (not just stages) for prod vs non-prod. Stage variables are useful but don't replace proper environment isolation.
Request/Response Transforms (REST API)
VTL mapping templates for REST API:
## Request transform: extract and reshape body
#set($body = $input.path('$'))
{
"userId": "$context.authorizer.claims.sub",
"itemName": "$body.name",
"timestamp": "$context.requestTime"
}Opinionated: VTL is painful to debug and maintain. For complex transforms, use a Lambda integration instead. Reserve VTL for simple cases like adding request context or status code mapping.
WebSocket APIs
# Create WebSocket API
aws apigatewayv2 create-api \
--name my-websocket-api \
--protocol-type WEBSOCKET \
--route-selection-expression '$request.body.action'
# Routes you typically need:
# $connect -- client connects (auth happens here)
# $disconnect -- client disconnects
# $default -- fallback for unmatched routes
# Custom routes -- matched by route-selection-expression
# Send message to connected client from backend
aws apigatewaymanagementapi post-to-connection \
--connection-id "abc123" \
--data '{"message": "hello"}' \
--endpoint-url "https://xyz.execute-api.us-east-1.amazonaws.com/prod"Key design decisions for WebSocket:
- Store connection IDs in DynamoDB (not in-memory)
- Use
$connectroute for authentication - Set idle timeout (default 10 min, max 2 hours)
- Max message size is 128 KB (frames up to 32 KB)
- Use API Gateway management API to push messages from backend
CORS Configuration
- HTTP API: Built-in CORS support via
cors-configuration. One command configures everything. - REST API: Requires manual OPTIONS method with mock integration on each resource, plus CORS headers on all integration responses. Use SAM/CDK to automate this -- doing it manually via CLI is error-prone.
Key rules: Never use wildcard origins in production. If using credentials, you must specify exact origins. For REST API with Lambda proxy integration, return CORS headers from your Lambda function, not from API Gateway.
See references/cors-recipes.md for complete configuration examples (CLI, CDK, SAM, CloudFormation), common CORS issues and fixes, and a production checklist.
Common CLI Commands
# List APIs
aws apigatewayv2 get-apis # HTTP/WebSocket APIs
aws apigateway get-rest-apis # REST APIs
# Test an endpoint
curl -H "Authorization: Bearer $TOKEN" https://abc123.execute-api.us-east-1.amazonaws.com/prod/items
# Get execution logs (must enable logging on stage first)
aws logs filter-log-events \
--log-group-name "API-Gateway-Execution-Logs_abc123/prod" \
--filter-pattern "ERROR"
# Enable execution logging (REST API)
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations \
op=replace,path=/accessLogSetting/destinationArn,value=arn:aws:logs:us-east-1:123456789:log-group:api-logs \
op=replace,path='/*/*/*/logging/loglevel',value=INFO
# Export API definition
aws apigateway get-export \
--rest-api-id abc123 \
--stage-name prod \
--export-type oas30 \
--accepts application/yaml api-spec.yamlAnti-Patterns
1. Using REST API when HTTP API suffices: Paying 3.5x more for features you don't use. Audit your feature requirements. 2. API keys as sole authentication: API keys are identifiers, not authenticators. Always pair with IAM, Cognito, or Lambda authorizers. 3. No throttling on public APIs: Without throttling, a single client can exhaust your account-level limit, affecting all APIs. 4. Deploying without stage-specific settings: Each stage should have its own logging, throttling, and Lambda alias configuration. 5. Large payloads through API Gateway: Payload limit is 10 MB. For file uploads, use pre-signed S3 URLs instead. 6. Ignoring the 29-second timeout: API Gateway has a hard 29-second integration timeout. Design for async patterns (return 202, poll/webhook) for long-running operations. 7. Not enabling CloudWatch Logs: Without execution logs, you cannot debug 5xx errors. Enable at minimum ERROR-level logging. 8. Wildcard CORS in production: AllowOrigins: * in production exposes your API to any origin. Specify exact allowed origins. 9. Complex VTL mapping templates: VTL is hard to test, debug, and maintain. If your transform is more than 10 lines, move it to Lambda. 10. Not using a custom domain: The default execute-api URL changes on redeployment (REST API). Custom domains provide stable URLs and allow API migration without client changes.
Cost Optimization
- HTTP API is 70% cheaper than REST API for the same traffic
- Enable REST API caching to reduce Lambda invocations (but adds ~$0.02/hour per GB)
- Use Lambda authorizer caching to avoid re-executing authorizer on every request
- For high-traffic APIs, consider CloudFront in front of API Gateway for additional caching
- Monitor 4xx errors -- wasted invocations from bad clients still cost money
Reference Files
references/authorizer-patterns.md-- Detailed authorizer configurations (JWT, Cognito, Lambda, IAM), trust policies, response formats, CDK examples, and SigV4 signingreferences/cors-recipes.md-- Complete CORS setup for REST and HTTP APIs (CLI, CDK, SAM, CloudFormation), common issues and fixes, production checklist
Related Skills
lambda-- Backend integration functions, authorizer implementationiam-- IAM policies for API Gateway access, SigV4 authorizationcloudfront-- CDN caching in front of API Gateway, custom domain routingnetworking-- VPC links, private API configuration, DNSsecurity-review-- Review API Gateway security posture, authorizer configuration, and WAF rules
API Gateway Authorizer Patterns
Detailed configuration examples for each API Gateway authorizer type. For guidance on when to use each, see the main SKILL.md.
JWT Authorizer (HTTP API) -- Recommended for Cognito/OIDC
The simplest authorizer for HTTP APIs when using Cognito or any OIDC-compliant identity provider.
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--name cognito-auth \
--jwt-configuration '{"Audience":["your-app-client-id"],"Issuer":"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX"}'Key points:
Audienceis your Cognito App Client ID (or OIDC client ID)Issuermust be the exact URL of the Cognito User Pool or OIDC provider- Identity source defaults to
$request.header.Authorization(Bearer token) - No Lambda function needed -- API Gateway validates the JWT directly
CDK Example
import { HttpApi, HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpJwtAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
const jwtAuthorizer = new HttpJwtAuthorizer('CognitoAuth', userPool.userPoolProviderUrl, {
jwtAudience: [userPoolClient.userPoolClientId],
});
httpApi.addRoutes({
path: '/items',
methods: [HttpMethod.GET],
integration: lambdaIntegration,
authorizer: jwtAuthorizer,
});Cognito Authorizer (REST API)
Built-in REST API authorizer that validates Cognito User Pool tokens directly.
aws apigateway create-authorizer \
--rest-api-id abc123 \
--name cognito-auth \
--type COGNITO_USER_POOLS \
--provider-arns arn:aws:cognito-idp:us-east-1:123456789:userpool/us-east-1_XXXXX \
--identity-source 'method.request.header.Authorization'CDK Example
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'Authorizer', {
cognitoUserPools: [userPool],
resultsCacheTtl: Duration.minutes(5),
});
api.root.addResource('items').addMethod('GET', lambdaIntegration, {
authorizer: auth,
authorizationType: apigateway.AuthorizationType.COGNITO,
});Lambda Authorizer (Custom Logic)
Use when you need to validate tokens from a non-OIDC provider, check custom headers, query parameters, or implement business-specific authorization logic.
REST API -- REQUEST Type (Recommended)
aws apigateway create-authorizer \
--rest-api-id abc123 \
--name custom-auth \
--type REQUEST \
--authorizer-uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789:function:my-authorizer/invocations \
--authorizer-result-ttl-in-seconds 300 \
--identity-source 'method.request.header.Authorization,context.httpMethod'REST API -- TOKEN Type
aws apigateway create-authorizer \
--rest-api-id abc123 \
--name token-auth \
--type TOKEN \
--authorizer-uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789:function:my-authorizer/invocations \
--authorizer-result-ttl-in-seconds 300 \
--identity-source 'method.request.header.Authorization'HTTP API -- Lambda Authorizer v2
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--authorizer-type REQUEST \
--authorizer-uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789:function:my-authorizer/invocations \
--authorizer-payload-format-version "2.0" \
--enable-simple-responses \
--name custom-authLambda Authorizer Trust Policy
The Lambda function used as an authorizer must allow API Gateway to invoke it:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:my-authorizer",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:123456789:abc123/authorizers/*"
}
}
}
]
}Lambda Authorizer Response Format
REST API (v1 format):
{
"principalId": "user123",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "arn:aws:execute-api:us-east-1:123456789:abc123/prod/GET/items"
}
]
},
"context": {
"userId": "user123",
"role": "admin"
}
}HTTP API (v2 simple format, with `enable-simple-responses`):
{
"isAuthorized": true,
"context": {
"userId": "user123",
"role": "admin"
}
}Best Practices for Lambda Authorizers
- Always cache results (300s default is good). Use REQUEST type over TOKEN type for REST API -- it provides more context and is more flexible.
- Keep authorizer functions fast -- they add latency to every uncached request. Target under 100ms.
- Return a Deny policy (REST) or
isAuthorized: false(HTTP) instead of throwing errors. Thrown errors result in 500s, not 403s. - Use identity source wisely -- it determines the cache key. Include all values that affect the auth decision.
IAM Authorization
Best for service-to-service communication. Uses SigV4 signing. No custom authorizer needed.
# REST API: set authorizationType on method
aws apigateway put-method \
--rest-api-id abc123 \
--resource-id xyz789 \
--http-method GET \
--authorization-type AWS_IAMIAM Policy for Callers
The calling service or role needs an IAM policy like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:123456789:abc123/prod/GET/items"
}
]
}SigV4 Signing Example (Python/boto3)
import requests
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials
import boto3
session = boto3.Session()
credentials = session.get_credentials().get_frozen_credentials()
request = AWSRequest(
method='GET',
url='https://abc123.execute-api.us-east-1.amazonaws.com/prod/items',
headers={'Host': 'abc123.execute-api.us-east-1.amazonaws.com'}
)
SigV4Auth(credentials, 'execute-api', 'us-east-1').add_auth(request)
response = requests.get(request.url, headers=dict(request.headers))Decision Matrix: Which Authorizer to Use
| Scenario | Recommended Authorizer |
|---|---|
| Web/mobile app with Cognito | JWT authorizer (HTTP API) or Cognito authorizer (REST API) |
| Third-party OIDC (Auth0, Okta) | JWT authorizer (HTTP API) |
| Custom token format | Lambda authorizer |
| Multi-header auth (API key + token) | Lambda authorizer (REQUEST type) |
| Service-to-service (internal) | IAM authorization |
| Public API with rate limiting | API keys (for tracking) + any authorizer above |
API Gateway CORS Recipes
Complete CORS configuration patterns for both REST and HTTP APIs, plus common issues and fixes.
HTTP API CORS (Simple)
HTTP API has built-in CORS support. One command configures everything:
aws apigatewayv2 update-api \
--api-id abc123 \
--cors-configuration \
AllowOrigins="https://example.com",AllowMethods="GET,POST,OPTIONS",AllowHeaders="Authorization,Content-Type",MaxAge=3600CDK Example (HTTP API)
import { HttpApi, CorsHttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
const httpApi = new HttpApi(this, 'Api', {
corsPreflight: {
allowOrigins: ['https://example.com', 'https://staging.example.com'],
allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.POST, CorsHttpMethod.PUT, CorsHttpMethod.DELETE],
allowHeaders: ['Authorization', 'Content-Type', 'X-Request-Id'],
exposeHeaders: ['X-Request-Id'],
maxAge: Duration.hours(1),
allowCredentials: true,
},
});CloudFormation / SAM (HTTP API)
Resources:
HttpApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: my-http-api
ProtocolType: HTTP
CorsConfiguration:
AllowOrigins:
- "https://example.com"
AllowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
AllowHeaders:
- Authorization
- Content-Type
MaxAge: 3600
AllowCredentials: trueREST API CORS (Manual Setup)
REST API requires manual CORS setup: an OPTIONS method with mock integration plus CORS headers on every integration response. This is error-prone by hand -- use SAM, CDK, or the console's "Enable CORS" button.
CDK Example (REST API)
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
const api = new apigateway.RestApi(this, 'Api', {
defaultCorsPreflightOptions: {
allowOrigins: ['https://example.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Authorization', 'Content-Type', 'X-Amz-Date', 'X-Api-Key'],
allowCredentials: true,
maxAge: Duration.hours(1),
},
});Manual CLI Setup (REST API)
For each resource that needs CORS:
# 1. Add OPTIONS method
aws apigateway put-method \
--rest-api-id abc123 \
--resource-id xyz789 \
--http-method OPTIONS \
--authorization-type NONE
# 2. Add mock integration
aws apigateway put-integration \
--rest-api-id abc123 \
--resource-id xyz789 \
--http-method OPTIONS \
--type MOCK \
--request-templates '{"application/json": "{\"statusCode\": 200}"}'
# 3. Add method response
aws apigateway put-method-response \
--rest-api-id abc123 \
--resource-id xyz789 \
--http-method OPTIONS \
--status-code 200 \
--response-parameters '{
"method.response.header.Access-Control-Allow-Headers": false,
"method.response.header.Access-Control-Allow-Methods": false,
"method.response.header.Access-Control-Allow-Origin": false
}'
# 4. Add integration response with CORS headers
aws apigateway put-integration-response \
--rest-api-id abc123 \
--resource-id xyz789 \
--http-method OPTIONS \
--status-code 200 \
--response-parameters '{
"method.response.header.Access-Control-Allow-Headers": "'Authorization,Content-Type,X-Amz-Date,X-Api-Key'",
"method.response.header.Access-Control-Allow-Methods": "'GET,POST,PUT,DELETE,OPTIONS'",
"method.response.header.Access-Control-Allow-Origin": "'https://example.com'"
}'
# 5. ALSO add CORS headers to your actual method integration responses (GET, POST, etc.)
# The OPTIONS preflight is not enough -- the actual response must also include
# Access-Control-Allow-Origin or the browser will reject it.SAM Template (REST API)
Resources:
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Authorization,Content-Type'"
AllowOrigin: "'https://example.com'"
AllowCredentials: true
MaxAge: "'3600'"Common CORS Issues and Fixes
1. "No 'Access-Control-Allow-Origin' header" Error
Cause: The response is missing the Access-Control-Allow-Origin header.
Fix (HTTP API): Ensure cors-configuration is set on the API.
Fix (REST API): You must add CORS headers to BOTH the OPTIONS method AND the actual method (GET, POST, etc.) integration responses. The OPTIONS preflight alone is not enough.
Fix (Lambda proxy integration): When using Lambda proxy integration, your Lambda function must return CORS headers in its response:
exports.handler = async (event) => {
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': 'https://example.com',
'Access-Control-Allow-Headers': 'Authorization,Content-Type',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
},
body: JSON.stringify({ data: 'hello' }),
};
};2. CORS Works for Simple Requests but Fails for Preflight
Cause: The OPTIONS method is missing or misconfigured.
Fix: Ensure the OPTIONS method exists on the resource, uses MOCK integration, and returns proper CORS headers. For HTTP API, the built-in CORS handles this automatically.
3. "Request header field X is not allowed by Access-Control-Allow-Headers"
Cause: The client is sending a header not listed in AllowHeaders.
Fix: Add the missing header to AllowHeaders. Common headers that must be explicitly allowed:
AuthorizationContent-TypeX-Amz-DateX-Api-KeyX-Amz-Security-Token- Any custom headers your app uses
4. CORS Fails When Using Cognito/JWT Authorizer
Cause: The authorizer rejects the OPTIONS preflight request (which has no Authorization header).
Fix (HTTP API): The built-in CORS handling runs before authorizers, so this should not happen. If it does, check that you haven't attached the authorizer to the OPTIONS route.
Fix (REST API): Set the OPTIONS method's authorization-type to NONE, even if other methods use an authorizer.
5. Wildcard Origin with Credentials
Cause: AllowOrigins: * combined with AllowCredentials: true.
Fix: Browsers reject this combination. You must specify exact origins when using credentials:
# WRONG
AllowOrigins="*",AllowCredentials=true
# CORRECT
AllowOrigins="https://example.com",AllowCredentials=true6. CORS Headers Duplicated (REST API with Lambda Proxy)
Cause: Both the API Gateway CORS configuration and the Lambda function return CORS headers, leading to duplicate headers that some browsers reject.
Fix: Choose one approach:
- Option A (recommended): Use Lambda proxy integration and return CORS headers from your Lambda only. Do not add CORS headers in the API Gateway integration response.
- Option B: Use non-proxy integration and handle CORS entirely in API Gateway mapping templates.
Production CORS Checklist
- [ ] Specify exact allowed origins (no wildcards in production)
- [ ] Include all required headers in
AllowHeaders - [ ] Set
MaxAgeto reduce preflight requests (3600 seconds is reasonable) - [ ] If using credentials (cookies, Authorization header), set
AllowCredentials: truewith specific origins - [ ] For REST API with Lambda proxy: return CORS headers from Lambda, not API Gateway
- [ ] Test preflight (OPTIONS) requests separately from actual requests
- [ ] Verify CORS works with your authorizer (OPTIONS must not require auth)
Related skills
FAQ
Should I default to REST API or HTTP API?
Default to HTTP API since it is cheaper and faster; use REST API only when you need WAF, request validation, API keys, VTL transforms, caching, or private APIs.
Are API keys a form of authentication?
No, API keys are for throttling and usage tracking, not authentication; always combine them with a real authorizer.