Skip to content

AWS API Gateway

API Gateway is a managed front door. A request arrives, is matched to a route or method, passes authorization, validation and throttling, and is forwarded to an integration (Lambda, an HTTP backend, or an AWS service). It comes in three products with different feature sets, so most early mistakes are picking the wrong one, or editing a REST API and forgetting that nothing is live until you deploy it.

TypePick it whenNotes
HTTP APIProxying to Lambda or HTTP, JWT authCheaper, lower latency
REST APIYou need keys, caching, WAF, validationMost features
WebSocket APIClients and server push messagesStateful connections

REST APIs also choose an endpoint type: edge-optimized (served through CloudFront), Regional, or private (reachable only through a VPC endpoint). HTTP and WebSocket APIs are Regional only.

Gotcha: HTTP APIs have no usage plans, API keys, response caching, request validation, WAF integration, or private endpoints. Check this list before choosing HTTP for the price, or you will migrate later.

IntegrationBackendNotes
Lambda proxyA Lambda functionEvent passed through as is
HTTP proxyA public HTTP URLRequest and response forwarded
AWS serviceSQS, SNS, Step Functions…REST uses mapping templates
MockNoneREST only; canned response
VPC linkPrivate load balancerReaches resources inside a VPC

Prefer proxy integrations: API Gateway forwards the request untouched and your code shapes the response. Non-proxy integrations (REST only) transform data with VTL mapping templates, which is powerful but hard to debug.

export const handler = async (event) => {
const { id } = event.pathParameters;
return {
statusCode: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ id }),
};
};

HTTP APIs default to payload format 2.0, where you may return a plain object and it is serialized as a 200 JSON response. REST APIs always need the statusCode/headers/body shape above.

Gotcha: A Lambda proxy response with a bad shape (body as an object, a missing statusCode) returns 502 Bad Gateway with “Malformed Lambda proxy response”. Always JSON.stringify the body.

APIRoute formCatch-all
RESTResource /items/{id} plus a method{proxy+}
HTTPRoute key GET /items/{id}$default
WebSocketRoute key from a message field$default

A {name} segment captures one path segment. A greedy {proxy+} captures the rest of the path and must be the last segment. In HTTP APIs the most specific matching route wins, and $default catches whatever matches nothing else.

Transform: AWS::Serverless-2016-10-31
Resources:
Fn:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs22.x
Handler: index.handler
CodeUri: src/
Events:
Get:
Type: HttpApi
Properties:
Path: /items/{id}
Method: GET

Gotcha: In a REST API, a /{proxy+} resource does not match the root /. Add a method on the root resource too, or GET / fails.

OptionAPI typesUse for
IAM (SigV4)REST, HTTP, WebSocketAWS callers, service-to-service
Cognito user poolRESTCognito-issued tokens
JWT authorizerHTTPAny OIDC or OAuth 2.0 issuer
Lambda authorizerREST, HTTP, WebSocketCustom token or header logic

A REST Lambda authorizer returns an IAM policy that allows or denies execute-api:Invoke. API Gateway caches that policy per identity source for a TTL (default 300 seconds, up to 3600). The optional context map is passed to your backend, and its values must be strings, numbers, or booleans.

export const handler = async (event) => {
const t = event.authorizationToken;
const ok = t === process.env.TOKEN;
return {
principalId: "user",
policyDocument: {
Version: "2012-10-17",
Statement: [{
Action: "execute-api:Invoke",
Effect: ok ? "Allow" : "Deny",
Resource: event.methodArn,
}],
},
};
};

HTTP API Lambda authorizers can instead use the simple response { isAuthorized: true } when simple responses are enabled.

Gotcha: With caching on, a policy scoped to event.methodArn is reused for the same token on every other route, and those calls fail with 403. Return a wildcard resource for the API, or set the TTL to 0.

LevelScopeDefault
AccountAll APIs in a Region10,000 rps, 5,000 burst
Stage or methodOne APISet per stage or method
Usage planPer API key (REST only)Rate, burst, and quota

Throttling uses a token bucket: the rate refills tokens and the burst is the bucket size. A throttled request gets 429 Too Many Requests. Some Regions (for example Cape Town, Milan, Jakarta) default to 2,500 rps and 1,250 burst.

Terminal window
aws apigateway create-usage-plan \
--name basic \
--throttle burstLimit=20,rateLimit=10 \
--quota limit=1000,period=DAY \
--api-stages apiId=abc123,stage=prod

Gotcha: API keys meter traffic, they do not authenticate it. A method only checks them when apiKeyRequired is on, and a missing or invalid key returns 403 Forbidden. Pair keys with a real authorizer.

ConceptRESTHTTP
Go liveCreate a deploymentAuto-deploy option
Default stageNamed by you$default
Stage variablesYesYes
Canary releasesYesNo

The invoke URL is https://{api-id}.execute-api.{region}.amazonaws.com/{stage}. The $default stage drops the stage segment from the path. Stage variables (such as a Lambda alias) are referenced as ${stageVariables.name}.

Terminal window
aws apigateway create-deployment \
--rest-api-id abc123 \
--stage-name prod

Custom domains need an ACM certificate: in the API’s Region for Regional APIs, and in us-east-1 for edge-optimized REST APIs. API mappings attach a stage to a domain path.

Gotcha: Editing a REST API changes nothing live until you create a new deployment to the stage. Run create-deployment (or redeploy in the console) after every change.

StatusTypical cause
400Request failed REST validation
403Authorizer denied, bad API key, WAF block
429Throttled
502Malformed or failed Lambda proxy response
504Integration exceeded its timeout

REST request validators check required parameters, headers, and body schemas, and reject bad requests with 400 before your backend runs. Integration timeouts return 504 once they pass the limit (29 seconds by default on REST).

HTTP APIs have built-in CORS configuration. For REST proxy integrations, API Gateway answers the OPTIONS preflight but your function must return the CORS headers itself.

const ORIGIN = "https://app.example.com";
export const handler = async () => ({
statusCode: 200,
headers: {
"access-control-allow-origin": ORIGIN,
},
body: "{}",
});

Gotcha: 403 “Missing Authentication Token” on a REST API usually means the path, method, or stage does not exist, not that you forgot a token. Check the URL, the HTTP verb, and the deployment.

RouteFires whenUse for
$connectConnection opensAuthenticate, store the ID
$disconnectConnection closesClean up stored IDs
$defaultNo route matchesFallback handler
Custom keySelection expression matchesDispatch by message action

The API-level route selection expression picks a JSON property from each message (for example ${request.body.action}) and matches its value to a route key. Your backend pushes data to a client by sending a request to POST https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/@connections/{connectionId}, signed with SigV4. A GET on the same URL reads connection status and a DELETE closes it.

Gotcha: Connections close after 10 idle minutes or a 2-hour lifetime, whichever comes first. Clients must reconnect, and you must remove stale connection IDs, or posts to them fail.

LimitRESTHTTP
Integration timeout29 s default30 s max
Payload size10 MB10 MB
Routes or resources300 resources300 routes
Stages per API1010
Authorizers per API1010

Most are adjustable except the payload size and the HTTP API timeout. REST Regional and private APIs can raise the integration timeout above 29 seconds, but that may cost you account-level throttle quota. Lambda’s own 15-minute limit does not help: the gateway gives up first, so move long work behind a queue or an async invocation.