API Gateway
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Commands and behaviour on this page are current for AWS CLI v2 (2.37 line) and the Amazon API Gateway documentation, all
checked on 2026-10-01; re-verify against aws --version and the linked reference pages before relying on them in a
pipeline. Examples use us-east-1, the placeholder account ID 111122223333, the placeholder domain example.com and
shell variables such as $API_ID instead of real identifiers. Prices are never stated here; see
Pricing model.
Bookshelf needs one public front door. The static site lives on S3 and CloudFront
(CloudFront and CDN Delivery), but the browser also calls the back end: catalog-api and orders-api run on ECS
behind the private Application Load Balancer alb-bookshelf (ECS and Fargate), and a few small
endpoints are Lambda functions (AWS Lambda). Amazon API Gateway is the managed service that
accepts those HTTPS requests, authenticates and throttles them, and forwards them to the right back end, so the
services behind it never see anonymous or excessive traffic. This page builds the HTTP API bookshelf-api in front of
a Lambda function and, through a VPC link, in front of the ALB; shows what a REST API adds and imports one from an
OpenAPI file; and covers authorizers, traffic control, custom domains, operations, limits and cost. The architectural
reasoning behind a gateway or a Backend for Frontend (when to have one, what belongs in it) is on
API Gateway and Backends for Frontends; this page is the AWS realization. The
Azure counterpart is API Management. Cloud-neutral comparisons of managed API and secret
managers are tracked in issue #184 and will not be repeated here.
API types: HTTP, REST and WebSocket
API Gateway offers three API types, and the first decision is which one to create, because the type cannot be changed afterwards.
-
HTTP APIs are the lean, low-cost option: routes, Lambda and HTTP/private integrations, JWT, Lambda and IAM authorizers, CORS, automatic deployments. They are the default for new JSON APIs.
-
REST APIs are the full-featured option: API keys and usage plans, per-client throttling, request validation, AWS WAF, caching, canary releases, private endpoints, resource policies, mapping templates, execution logs, X-Ray and response streaming. They cost more per request.
-
WebSocket APIs keep a persistent two-way connection and route JSON messages to integrations by a route selection expression; they are for chat, live dashboards and notifications.
| Feature | HTTP API | REST API | WebSocket API |
|---|---|---|---|
Endpoint types |
Regional |
Edge-optimized, Regional, Private |
Regional |
Authorization |
JWT, Lambda, IAM (Cognito through a JWT authorizer) |
IAM, Cognito user pool, Lambda, resource policies (JWT through a Lambda authorizer) |
IAM, Lambda authorizer on |
AWS WAF |
No (put CloudFront with WAF in front) |
Yes |
No |
API keys, usage plans, per-client throttling |
No |
Yes |
No |
Caching |
No |
Yes |
No |
Request validation, body transformation |
No (parameter mapping only) |
Yes |
Model-based validation on routes |
Canary release deployments |
No (use stages, or routing rules on a custom domain) |
Yes |
No |
Mutual TLS on custom domains |
Yes |
Yes |
Not covered here |
Response streaming |
No |
Yes (proxy integrations) |
Not applicable (bidirectional) |
Deployment model |
Optional automatic deployments per stage |
Explicit deployments to stages |
Explicit deployments to stages |
Private integrations (VPC link) |
ALB, NLB, AWS Cloud Map |
ALB and NLB |
NLB through a VPC link |
Sources for the table are the guide
Choose between REST APIs and HTTP APIs
and the WebSocket developer guide; features change, so treat the table as a starting point. Practical rule for
Bookshelf: use an HTTP API unless you need API keys with per-client quotas (a partner-facing API), WAF directly on the
API, caching, request validation at the edge of the service or a private-only API; in those cases use a REST API.
Older tutorials treat REST APIs as the only product and add API Gateway "proxy" resources and mapping templates by
default; the current approach for most JSON back ends is an HTTP API with a $default or ANY proxy route and
validation in the application.
How a request flows
Every API type follows the same pipeline. The ordering matters when you debug, because each stage can reject the request with its own status code.
TLS, ACM certificate"] D --> M["API mapping or routing rule
picks API and stage"] M --> S["Stage
throttling, logging, variables"] S --> R["Route match
GET /books, ANY /catalog/proxy"] R --> A{"Authorizer
JWT, Lambda, IAM, Cognito"} A -- "denied: 401 or 403" --> X["Error returned
to client"] A -- "allowed" --> I["Integration
Lambda, HTTP, VPC link"] I --> B["Back end
Lambda or ALB to ECS"] B --> I I --> C
In words: the client resolves the custom domain (or the default execute-api endpoint), API Gateway terminates TLS,
selects the API and stage (through an API mapping or a routing rule), applies stage-level throttling, matches the
route (HTTP method plus path), runs the route’s authorizer, and only then calls the integration. A throttled
request fails before the authorizer with 429; an unmatched route returns 404; an authorizer denial returns 401
(no or invalid credentials) or 403 (valid credentials, not permitted); a back end that fails or is too slow returns
500, 502, 503 or 504.
Concepts for HTTP APIs
-
API: the container, with a
ProtocolTypeofHTTPorWEBSOCKET. Its default endpoint ishttps://<api-id>.execute-api.<region>.amazonaws.com. -
Route: a route key such as
GET /books,POST /orders,ANY /catalog/{proxy+}(a greedy path variable that matches the rest of the path) or the catch-all$default. The most specific route wins. -
Integration: what a route calls. Types are
AWS_PROXY(Lambda and other AWS service actions),HTTP_PROXY(a public URL, or a private ALB/NLB/Cloud Map target through a VPC link).MOCKintegrations exist only on REST APIs. -
Stage: a named, deployed snapshot such as
prod, with its own throttling, logging and variables. The special$defaultstage serves the root path with no stage prefix in the URL. A stage with auto-deploy enabled publishes route and integration changes immediately. -
Authorizer: JWT, Lambda (
REQUEST) or IAM, attached per route. -
Payload format version: for Lambda proxy integrations,
2.0(the simpler event withrequestContext.http,cookiesand automatic response shaping) or1.0(the REST API event). Use2.0for new HTTP APIs.
Prerequisites for Bookshelf
export AWS_REGION=us-east-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# A throwaway Lambda used for the first API (see lambda.adoc for creating functions)
export FN_NAME=bookshelf-ping
export FN_ARN=$(aws lambda get-function --function-name "$FN_NAME" \
--query Configuration.FunctionArn --output text)
# The private ALB that fronts catalog-api and orders-api on ECS
export ALB_ARN=$(aws elbv2 describe-load-balancers --names alb-bookshelf \
--query 'LoadBalancers[0].LoadBalancerArn' --output text)
export LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn "$ALB_ARN" \
--query 'Listeners[?Port==`443`].ListenerArn | [0]' --output text)
Derived from the aws lambda get-function and
aws elbv2 describe-listeners reference
pages. The function bookshelf-ping is any Lambda that returns a small JSON body; create one from
AWS Lambda. The ALB and its listener come from ECS and Fargate and
Load Balancers and Gateways.
Permissions to build and run the API
The identity that creates APIs needs the apigateway:* actions on the API Gateway management ARNs (these have no
account ID and no execute-api prefix) and a few neighbours. A least-privilege policy for the Bookshelf deployer, to
be narrowed further per environment:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ManageBookshelfApis",
"Effect": "Allow",
"Action": ["apigateway:GET", "apigateway:POST", "apigateway:PUT", "apigateway:PATCH", "apigateway:DELETE"],
"Resource": [
"arn:aws:apigateway:us-east-1::/apis",
"arn:aws:apigateway:us-east-1::/apis/*",
"arn:aws:apigateway:us-east-1::/vpclinks",
"arn:aws:apigateway:us-east-1::/vpclinks/*",
"arn:aws:apigateway:us-east-1::/domainnames",
"arn:aws:apigateway:us-east-1::/domainnames/*",
"arn:aws:apigateway:us-east-1::/tags/*"
]
},
{
"Sid": "AllowApiToInvokeLambda",
"Effect": "Allow",
"Action": ["lambda:AddPermission", "lambda:RemovePermission", "lambda:GetPolicy", "lambda:GetFunction"],
"Resource": "arn:aws:lambda:us-east-1:111122223333:function:bookshelf-*"
},
{
"Sid": "AccessLogGroups",
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:DescribeLogGroups", "logs:PutRetentionPolicy", "logs:DeleteLogGroup"],
"Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/apigateway/bookshelf-*"
},
{
"Sid": "ReadNetworkingForVpcLink",
"Effect": "Allow",
"Action": ["ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeVpcs",
"elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeListeners"],
"Resource": "*"
}
]
}
Turning on access logging additionally needs the CloudWatch Logs delivery actions (logs:CreateLogDelivery,
logs:PutResourcePolicy and friends) listed in
Configure logging for HTTP APIs; they do
not support resource-level restriction. Creating a VPC link may also need EC2 network-interface permissions or a
service-linked role the first time in an account; if creation fails with an authorization error, compare the denied
action against the VPC link page linked in the references. The invoke permissions of callers are separate and are
covered in IAM (SigV4) authorization. To grant a CI pipeline these rights without long-lived keys, see
Identity and Access.
Creating an HTTP API in front of Lambda
In the AWS Management Console
-
Choose the Region in the selector (
us-east-1) and open API Gateway. -
Under HTTP API choose Build, then Add integration, pick Lambda, select the function
bookshelf-pingand name the APIbookshelf-api. -
Choose Next. In Configure routes keep the suggested route (
ANY /bookshelf-ping) or edit it toGET /ping. -
In Configure stages keep the
$defaultstage with Auto-deploy on, choose Next, then Create. -
Open the API and copy the Invoke URL (
https://<api-id>.execute-api.us-east-1.amazonaws.com). Open it in a browser or call it withcurl. The console adds the resource policy on the function that lets API Gateway invoke it. -
In Monitor → Logging, select the
$defaultstage and turn on Access logging with a CloudWatch Logs log group (create/aws/apigateway/bookshelf-apifirst) and the JSON format.
With the AWS CLI
The fastest path is quick create: one call creates the API, a $default route, a Lambda proxy integration and an
auto-deploying $default stage.
export API_ID=$(aws apigatewayv2 create-api --name bookshelf-api --protocol-type HTTP \
--target "$FN_ARN" --query ApiId --output text)
# Quick create does not add the invoke permission; do it with a source ARN scoped to this API
aws lambda add-permission --function-name "$FN_NAME" --statement-id apigw-invoke-"$API_ID" \
--action lambda:InvokeFunction --principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$AWS_REGION:$ACCOUNT_ID:$API_ID/*/*/*"
export API_URL=$(aws apigatewayv2 get-api --api-id "$API_ID" --query ApiEndpoint --output text)
curl -s "$API_URL/"
Derived from aws apigatewayv2 create-api
and aws lambda add-permission. The --target
shortcut gives every method and path to the same function. For a real API, create the pieces explicitly so each route
can have its own integration, authorizer and throttling:
export API_ID=$(aws apigatewayv2 create-api --name bookshelf-api --protocol-type HTTP \
--query ApiId --output text)
# Lambda proxy integration, payload format 2.0
export PING_INT=$(aws apigatewayv2 create-integration --api-id "$API_ID" \
--integration-type AWS_PROXY --integration-uri "$FN_ARN" \
--payload-format-version 2.0 --query IntegrationId --output text)
aws apigatewayv2 create-route --api-id "$API_ID" --route-key 'GET /ping' \
--target "integrations/$PING_INT"
# Log group, then a stage with auto-deploy and access logs
aws logs create-log-group --log-group-name /aws/apigateway/bookshelf-api
aws logs put-retention-policy --log-group-name /aws/apigateway/bookshelf-api --retention-in-days 30
aws apigatewayv2 create-stage --api-id "$API_ID" --stage-name prod --auto-deploy \
--access-log-settings '{"DestinationArn":"arn:aws:logs:us-east-1:111122223333:log-group:/aws/apigateway/bookshelf-api","Format":"{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"routeKey\":\"$context.routeKey\",\"status\":\"$context.status\",\"integrationStatus\":\"$context.integrationStatus\",\"responseLength\":\"$context.responseLength\",\"error\":\"$context.error.message\"}"}'
aws lambda add-permission --function-name "$FN_NAME" --statement-id apigw-invoke-"$API_ID" \
--action lambda:InvokeFunction --principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$AWS_REGION:$ACCOUNT_ID:$API_ID/*/GET/ping"
curl -s "https://$API_ID.execute-api.$AWS_REGION.amazonaws.com/prod/ping"
Derived from
create-integration,
create-route and
create-stage. Note the stage name in
the URL (/prod/ping); with $default it would be /ping. The Lambda permission is scoped by --source-arn to the
single API, stage wildcard, method and path so no other API in the account can invoke the function; a quick-create
wildcard like ///* is fine for a demo but too broad for production.
A Lambda handler for payload format 2.0
package com.example.bookshelf.edge;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
import java.util.Map;
public class PingHandler implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
@Override
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
var claims = event.getRequestContext().getAuthorizer() == null
? Map.<String, String>of()
: event.getRequestContext().getAuthorizer().getJwt().getClaims();
return APIGatewayV2HTTPResponse.builder()
.withStatusCode(200)
.withHeaders(Map.of("content-type", "application/json"))
.withBody("{\"pong\":true,\"sub\":\"" + claims.getOrDefault("sub", "anonymous") + "\"}")
.build();
}
}
The types come from com.amazonaws:aws-lambda-java-events; see
Using Lambda with API Gateway. Authenticated
claims arrive in requestContext.authorizer.jwt.claims; Lambda packaging, SnapStart for Java and the
aws-serverless-java-container adapter for Spring Boot are on AWS Lambda.
Fronting the ALB with a VPC link
catalog-api and orders-api stay private: the ALB alb-bookshelf is internal, with no public address. An HTTP API
reaches it through a VPC link (V2), which makes API Gateway create and manage elastic network interfaces in your
subnets. The link is immutable after creation (subnets and security groups cannot be changed), is reusable across
APIs, becomes INACTIVE after 60 days without traffic (its interfaces are deleted and requests fail until it is
reprovisioned), and requires that the link, the load balancer and the API are in the same account.
In the AWS Management Console
-
In API Gateway choose VPC links → Create, choose the HTTP API type (V2), name it
bookshelf-vpclink, select the VPC, two private subnets in different Availability Zones and a security groupsg-bookshelf-vpclink, then Create. Wait until the status is Available (a few minutes). -
In the EC2 console, edit the ALB’s security group so it allows inbound HTTPS from
sg-bookshelf-vpclink. -
Open
bookshelf-api→ Integrations → Create. Choose Private resource, ALB/NLB, select the load balanceralb-bookshelfand its HTTPS listener, the VPC link, and Advanced settings → Secure server name (catalog.internal.example.com, the name on the certificate) because private integrations default to plain HTTP. -
In Routes create
ANY /catalog/{proxy+}and attach that integration. Under the integration’s Parameter mapping overwrite the path with/${request.path.proxy}if the application does not expect the/catalogprefix. -
Open the stage and check the Invoke URL; test with
curl "$API_URL/catalog/books".
With the AWS CLI
export VPC_ID=vpc-0123456789abcdef0 # placeholders; use your own
export SUBNET_A=subnet-0aaa1111bbbb22223 SUBNET_B=subnet-0ccc3333dddd44445
export VPCLINK_SG=$(aws ec2 create-security-group --group-name sg-bookshelf-vpclink \
--description "API Gateway VPC link" --vpc-id "$VPC_ID" --query GroupId --output text)
export VPC_LINK_ID=$(aws apigatewayv2 create-vpc-link --name bookshelf-vpclink \
--subnet-ids "$SUBNET_A" "$SUBNET_B" --security-group-ids "$VPCLINK_SG" \
--query VpcLinkId --output text)
aws apigatewayv2 get-vpc-link --vpc-link-id "$VPC_LINK_ID" --query VpcLinkStatus # wait for AVAILABLE
# Let the VPC link reach the ALB listener
aws ec2 authorize-security-group-ingress --group-id "$ALB_SG" --protocol tcp --port 443 \
--source-group "$VPCLINK_SG"
export CATALOG_INT=$(aws apigatewayv2 create-integration --api-id "$API_ID" \
--integration-type HTTP_PROXY --integration-method ANY \
--connection-type VPC_LINK --connection-id "$VPC_LINK_ID" \
--integration-uri "$LISTENER_ARN" --payload-format-version 1.0 \
--tls-config ServerNameToVerify=catalog.internal.example.com \
--request-parameters 'overwrite:path=/$request.path.proxy' \
--query IntegrationId --output text)
aws apigatewayv2 create-route --api-id "$API_ID" --route-key 'ANY /catalog/{proxy+}' \
--target "integrations/$CATALOG_INT"
Derived from
aws apigatewayv2 create-vpc-link,
create-integration and
Create private
integrations for HTTP APIs. The $ALB_SG variable is the ALB’s security group ID. For private integrations API
Gateway includes the stage name in the request path sent to the back end (a request to /prod/catalog/books arrives
as prod/catalog/books/…), which is why the integration above overwrites the path; with the $default stage there
is no prefix to remove. The same VPC link can serve a second integration for orders-api on a different ALB listener
rule. Private integrations also work with Network Load Balancers and, for ECS Service Connect or service discovery,
with AWS Cloud Map services; pick the combination with
Load Balancers and Gateways and ECS Deployments, Scaling and Networking.
REST APIs
A REST API is more verbose than an HTTP API: resources and methods instead of route keys, a deployment you create explicitly, and a stage that points at a deployment. You get more in return.
Importing an OpenAPI definition
openapi: "3.0.1"
info:
title: bookshelf-partner-api
version: "1.0"
paths:
/books:
get:
operationId: listBooks
parameters:
- name: author
in: query
required: false
schema: { type: string }
responses:
"200": { description: OK }
security:
- api_key: []
x-amazon-apigateway-request-validator: params
x-amazon-apigateway-integration:
type: http_proxy
httpMethod: GET
uri: https://catalog.internal.example.com/books
connectionType: VPC_LINK
connectionId: "${stageVariables.vpcLinkId}"
passthroughBehavior: when_no_match
components:
securitySchemes:
api_key:
type: apiKey
name: x-api-key
in: header
x-amazon-apigateway-request-validators:
params:
validateRequestParameters: true
validateRequestBody: false
The x-amazon-apigateway-* extensions describe integrations, validators, authorizers and CORS. HTTP APIs import
OpenAPI 3.0 with aws apigatewayv2 import-api --body file://openapi.yaml (and ignore unsupported extensions with
warnings); REST APIs use aws apigateway import-rest-api. See
OpenAPI extensions for
API Gateway.
In the AWS Management Console
-
Open API Gateway → REST API → Import (or Build for a blank API). Choose Import API, upload
openapi.yaml, set the API endpoint type (Regional for Bookshelf) and choose Create API. -
Review Resources: each path and method shows its integration request, authorizer and request validator.
-
Choose Deploy API, New stage →
prod, and Deploy. The stage page shows the Invoke URL. -
In the stage Stage variables tab add
vpcLinkId(with the ID of a REST-compatible VPC link), and in Logs and tracing turn on access logging, optional execution logging and X-Ray tracing. -
In Usage plans create
partnerswith a rate, burst and monthly quota, attach theprodstage, and add an API key.
With the AWS CLI
export REST_ID=$(aws apigateway import-rest-api --body fileb://openapi.yaml \
--parameters endpointConfigurationTypes=REGIONAL --query id --output text)
# A deployment is an immutable snapshot; a stage points at it
aws apigateway create-deployment --rest-api-id "$REST_ID" --stage-name prod \
--variables vpcLinkId="$VPC_LINK_ID" --description "first deployment"
# Update the definition later, then redeploy
aws apigateway put-rest-api --rest-api-id "$REST_ID" --mode overwrite --body fileb://openapi.yaml
aws apigateway create-deployment --rest-api-id "$REST_ID" --stage-name prod
# Stage settings: method-level throttling and metrics
aws apigateway update-stage --rest-api-id "$REST_ID" --stage-name prod --patch-operations \
op=replace,path=/*/*/throttling/rateLimit,value=100 \
op=replace,path=/*/*/throttling/burstLimit,value=50 \
op=replace,path=/*/*/metrics/enabled,value=true
Derived from aws apigateway
import-rest-api, create-deployment,
put-rest-api and
update-stage. A frequent REST API
surprise: editing a method or integration changes nothing until you create a new deployment; HTTP APIs with auto-deploy
avoid that step. Stage variables (up to 100 per stage, 512 characters per value) let one definition target
dev, test and prod back ends, and are referenced as ${stageVariables.name} in integration URIs and mapping
templates; do not put secrets in them (see Secrets Manager, Parameter Store and KMS).
Canary releases and response streaming
A canary release deploys a new deployment to a percentage of a REST API stage’s traffic while the rest keeps hitting the previous one, with separate metrics and logs:
aws apigateway create-deployment --rest-api-id "$REST_ID" --stage-name prod \
--canary-settings percentTraffic=10,useStageCache=false
# Looks healthy: promote it (the canary deployment becomes the stage deployment), then end the canary
aws apigateway update-stage --rest-api-id "$REST_ID" --stage-name prod \
--patch-operations op=copy,from=/canarySettings/deploymentId,path=/deploymentId \
op=remove,path=/canarySettings
Derived from Set up a canary release deployment. The console’s Promote canary action does the same promotion. HTTP APIs have no canary; use two stages, weighted Lambda aliases (AWS Lambda), or weighted ALB target groups.
Response streaming (REST APIs only, a recent addition) sets the integration’s responseTransferMode to STREAM for
HTTP_PROXY and AWS_PROXY integrations, so API Gateway forwards bytes as the back end produces them. It lowers
time to first byte for generative AI chat and server-sent events, allows responses beyond 10 MB and beyond the 29
second integration timeout, up to 15 minutes, and is billed separately. It does not support caching, API Gateway
content encoding or VTL response mapping, and request streaming is not supported. Streams hit an idle timeout of 5
minutes on Regional and private endpoints (30 seconds on edge-optimized). Details:
Stream the integration
response; for Lambda streaming itself see AWS Lambda.
Private APIs
A REST API with the Private endpoint type is reachable only from a VPC through an interface VPC endpoint for
execute-api (or through AWS PrivateLink from other networks). Its resource policy decides which VPCs, VPC
endpoints, accounts or IP ranges may call it, and the API is not callable until that policy exists and the API is
deployed. For Bookshelf’s internal admin API:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "execute-api:/*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "execute-api:/*",
"Condition": {
"StringNotEquals": { "aws:SourceVpce": "vpce-0123456789abcdef0" }
}
}
]
}
Apply it with aws apigateway update-rest-api --rest-api-id "$REST_ID" --patch-operations
op=replace,path=/policy,value='<escaped policy JSON>' and redeploy. The pattern (an explicit Allow plus a Deny
unless the call arrives through your endpoint) is the documented one:
Control access to a
REST API with resource policies. HTTP APIs cannot be private; keep an HTTP API’s back end private with a VPC link
instead, as above.
WebSocket APIs
A WebSocket API receives JSON messages and chooses a route from a route selection expression such as
${request.body.action}. Three routes are predefined: $connect (the place to attach an authorizer, because
authentication happens once when the connection opens), $disconnect and $default (no matching route). Your back end
pushes data to a client by calling the connection URL of the @connections API with the connection ID it stored
(typically in DynamoDB, Amazon DynamoDB).
export WS_ID=$(aws apigatewayv2 create-api --name bookshelf-live --protocol-type WEBSOCKET \
--route-selection-expression '$request.body.action' --query ApiId --output text)
export WS_INT=$(aws apigatewayv2 create-integration --api-id "$WS_ID" --integration-type AWS_PROXY \
--integration-uri "arn:aws:apigateway:$AWS_REGION:lambda:path/2015-03-31/functions/$FN_ARN/invocations" \
--query IntegrationId --output text)
aws apigatewayv2 create-route --api-id "$WS_ID" --route-key '$connect' --target "integrations/$WS_INT"
aws apigatewayv2 create-route --api-id "$WS_ID" --route-key '$disconnect' --target "integrations/$WS_INT"
aws apigatewayv2 create-stage --api-id "$WS_ID" --stage-name prod --auto-deploy
Derived from aws apigatewayv2
create-api and the
WebSocket API
overview. Limits to design around: connections end after 10 minutes of idleness and after 2 hours in total (clients must
reconnect), and binary frames are not supported on WebSocket APIs. Bookshelf’s notifier could push order status over a
WebSocket, but Server-Sent Events over a REST API with response streaming, or polling, are often simpler; choose
WebSocket when the client also sends frequent messages.
Authorization and authentication
| Mechanism | How it works | Choose it when |
|---|---|---|
JWT authorizer (HTTP APIs) |
API Gateway validates a JWT from an OpenID Connect or OAuth 2.0 provider: signature (RSA algorithms, key fetched
from the issuer’s |
Clients sign in with Cognito, Auth0, Keycloak, Entra ID or the Bookshelf’s own Spring Authorization Server and send a bearer token. The default for user-facing APIs. |
Lambda authorizer |
Your function receives the token or request attributes and returns allow/deny plus context. HTTP APIs support a simple boolean response; REST APIs expect an IAM policy document. Results can be cached by identity source. |
Opaque tokens, API keys checked against a database, multi-tenant rules, or JWT validation on REST APIs. |
IAM (SigV4) |
The caller signs the request with AWS credentials; API Gateway checks an identity-based policy for |
Service-to-service calls inside AWS, partner accounts that assume a role, and CLI or SDK clients. |
Cognito user pool authorizer (REST APIs) |
API Gateway validates the access or ID token of a Cognito user pool. On HTTP APIs use a JWT authorizer with the pool as issuer. |
You use Cognito and a REST API. |
Resource policy (REST APIs) |
A JSON policy on the API restricts source accounts, IP ranges or VPC endpoints. |
Private APIs and IP allow-lists. |
Mutual TLS |
Clients present a certificate that is validated against a truststore in S3 on the custom domain. |
Machine-to-machine clients with certificates. |
API keys are not in this table on purpose; see API keys are not authentication.
JWT authorizer with an OIDC provider
The token arrives in the Authorization header. The token types, scopes and audiences behind the checks above are
explained in JWT and the JOSE Family, OpenID Connect,
ID Tokens vs. Access Tokens and Scopes, Claims and Permissions;
discovery and the jwks_uri are in Discovery, Metadata and Client Registration, and a
client-credentials flow for service callers in Client Credentials and Client Authentication.
API Gateway cannot tell an access token from an ID token, so require scopes on routes (or a dedicated audience)
rather than accepting any validly signed token.
In the AWS Management Console
-
Open
bookshelf-api→ Authorization → Manage authorizers → Create. -
Choose JWT, name it
bookshelf-oidc, keep Identity source$request.header.Authorization, set Issuer URL to the provider’s issuer (for Cognitohttps://cognito-idp.us-east-1.amazonaws.com/<user-pool-id>) and add the Audience (the app client ID or the API identifier). -
Back on Authorization → Attach authorizers to routes, select a route such as
ANY /catalog/{proxy+}, pickbookshelf-oidcand choose Attach authorizer. -
Open the route → Authorization and add Authorization scopes such as
catalog.read.
With the AWS CLI
export AUTHZ_ID=$(aws apigatewayv2 create-authorizer --api-id "$API_ID" --name bookshelf-oidc \
--authorizer-type JWT --identity-source '$request.header.Authorization' \
--jwt-configuration Audience=bookshelf-web,Issuer=https://auth.example.com/ \
--query AuthorizerId --output text)
export ROUTE_ID=$(aws apigatewayv2 get-routes --api-id "$API_ID" \
--query "Items[?RouteKey=='ANY /catalog/{proxy+}'].RouteId | [0]" --output text)
aws apigatewayv2 update-route --api-id "$API_ID" --route-id "$ROUTE_ID" \
--authorization-type JWT --authorizer-id "$AUTHZ_ID" --authorization-scopes catalog.read
Derived from aws apigatewayv2
create-authorizer and Control
access to HTTP APIs with JWT authorizers. Use the issuer exactly as in the token’s iss claim, including any trailing
slash. Quotas to know: 10 authorizers per API, 50 audiences per authorizer, 10 scopes per route, and a 1.5 second
timeout when API Gateway fetches the discovery document or key set. After key rotation allow a grace period during which
both keys are valid, because API Gateway may use a cached key for up to two hours.
The back end should still validate the token (defense in depth) and enforce fine-grained authorization; the gateway check only stops anonymous traffic early. With Spring Boot as a resource server that is a single issuer property, see Spring Security and Authorization Server & Social Login:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/
Lambda authorizer
For tokens that are not JWTs, or for rules API Gateway cannot express, attach a REQUEST authorizer. With payload
format 2.0 and simple responses the function returns a boolean and optional context passed to the integration:
aws apigatewayv2 create-authorizer --api-id "$API_ID" --name bookshelf-lambda-authz \
--authorizer-type REQUEST --identity-source '$request.header.x-bookshelf-token' \
--authorizer-uri "arn:aws:apigateway:$AWS_REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$AWS_REGION:$ACCOUNT_ID:function:bookshelf-authz/invocations" \
--authorizer-payload-format-version 2.0 --enable-simple-responses \
--authorizer-result-ttl-in-seconds 300
aws lambda add-permission --function-name bookshelf-authz --statement-id apigw-authz \
--action lambda:InvokeFunction --principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$AWS_REGION:$ACCOUNT_ID:$API_ID/authorizers/*"
Derived from the HTTP API
Lambda authorizer guide. The function must answer within 10 seconds and returns
{ "isAuthorized": true, "context": { "tenant": "acme" } }; the cache key is built from the identity sources, so keep
them to the values that actually decide the answer. REST APIs use TOKEN or REQUEST authorizers that must return an
IAM policy document instead.
IAM (SigV4) authorization
For an internal caller (for example orders-api calling the admin API), set the route’s authorization type to
AWS_IAM and give the caller’s role permission to invoke it:
aws apigatewayv2 update-route --api-id "$API_ID" --route-id "$ROUTE_ID" --authorization-type AWS_IAM
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:111122223333:abc123defg/prod/GET/admin/*"
}
]
}
Attach the policy to the task role or Lambda execution role of the caller
(Identity and Access); the resource format is
arn:aws:execute-api:<region>:<account>:<api-id>/<stage>/<METHOD>/<path>, with abc123defg a placeholder API ID. The
caller must sign requests with SigV4 for the service name execute-api; the AWS SDK for Java 2.x does this with its
AwsV4HttpSigner, and curl with --aws-sigv4 "aws:amz:us-east-1:execute-api" plus temporary credentials. Reference:
Control access to HTTP APIs
with IAM authorization.
Cognito
Amazon Cognito user pools are managed OIDC providers (sign-up, sign-in, MFA, social federation) and identity
pools exchange tokens for AWS credentials. For a Bookshelf API, a user pool is a reasonable issuer: point a JWT
authorizer at https://cognito-idp.us-east-1.amazonaws.com/<user-pool-id> with the app client ID as audience. Cognito
access tokens carry client_id (not aud) and scope, which the JWT authorizer handles. The protocol side
(authorization code with PKCE for the SPA) is in Authorization Code and PKCE and
Browser-Based Apps (SPAs); the IAM-side summary is in Identity and Access.
Traffic control
Throttling
API Gateway uses a token bucket per Region per account: 10,000 requests per second steady state with a bucket of 5,000 across all APIs in the account in most Regions, the same pool for every API type, and Regions launched later start lower (2,500 and 1,250). The rate can be raised by request; the burst cannot be changed independently. You can set lower targets per stage and per route so one noisy API does not use the account’s whole allowance:
# HTTP API: stage default and one route
aws apigatewayv2 update-stage --api-id "$API_ID" --stage-name prod \
--default-route-settings ThrottlingBurstLimit=200,ThrottlingRateLimit=100
aws apigatewayv2 update-stage --api-id "$API_ID" --stage-name prod \
--route-settings '{"POST /orders":{"ThrottlingBurstLimit":50,"ThrottlingRateLimit":20}}'
Derived from Throttle requests to
your HTTP APIs. Throttles are best-effort targets, not guarantees; clients receive 429 Too Many Requests and should
retry with exponential backoff and jitter. A route-level limit cannot exceed the account limit. REST APIs offer the same
levels (account, stage, method) plus per-client limits through usage plans.
Usage plans and API keys
A REST API usage plan defines a throttle (rate and burst) and an optional quota (requests per day, week or month)
and applies it to the API keys attached to the plan, for example a partners plan for third-party integrators:
export PLAN_ID=$(aws apigateway create-usage-plan --name partners \
--throttle burstLimit=20,rateLimit=10 --quota limit=100000,period=MONTH \
--api-stages apiId="$REST_ID",stage=prod --query id --output text)
export KEY_ID=$(aws apigateway create-api-key --name partner-acme --enabled --query id --output text)
aws apigateway create-usage-plan-key --usage-plan-id "$PLAN_ID" --key-id "$KEY_ID" --key-type API_KEY
aws apigateway get-api-key --api-key "$KEY_ID" --include-value --query value --output text
Derived from aws apigateway
create-usage-plan. Methods must have API key required set (apiKeyRequired: true, or the security: api_key
entry in the OpenAPI above), and the key is sent in the x-api-key header. API keys and usage plans exist only for REST
APIs.
API keys are not authentication
An API key identifies a client for metering and throttling; it is a shared, long-lived string that ends up in configuration files, logs and mobile apps. It does not identify a user, is not scoped, and offers no signature or expiry. Use it to meter and throttle, always together with a real authorizer (JWT, IAM or Lambda). The official documentation says the same.
AWS WAF
REST APIs support attaching an AWS WAF web ACL to a stage (managed rule groups, rate-based rules, IP sets). HTTP APIs
do not; to protect an HTTP API with WAF, put CloudFront in front of it with a WAF web ACL and have the API accept only
traffic carrying a secret header or coming through CloudFront (see CloudFront and CDN Delivery), and disable
the default execute-api endpoint (below) so the API cannot be called around the CDN.
Request handling
CORS
Browsers call bookshelf-api from the CloudFront site, so cross-origin requests need the right headers. With an HTTP
API, configure CORS on the API and API Gateway answers preflight OPTIONS requests itself (an OPTIONS route is not
needed) and adds the headers to responses:
aws apigatewayv2 update-api --api-id "$API_ID" --cors-configuration \
AllowOrigins=https://www.example.com,AllowMethods=GET,POST,AllowHeaders=authorization,content-type,MaxAge=3600
Derived from Configure CORS for an HTTP
API. When CORS is configured on the API, API Gateway ignores CORS headers set by the integration; do not set them in both
places. Use specific origins; * cannot be combined with credentials. On REST APIs you add an OPTIONS mock method
and return the CORS headers from the integration or from gateway responses (including error responses, or a 401
will look like a CORS failure in the browser). For Spring Boot behind the gateway see the Spring Security page above.
Validation, mapping and transformation
-
HTTP APIs have parameter mapping only: modify (overwrite, append, remove) request headers, query strings and the path using context and stage variables, and map response headers and status codes. Everything else (body validation) belongs in the application.
-
REST APIs add request validators (check required parameters and a JSON Schema model before the integration is invoked and return
400cheaply), models, and VTL mapping templates to reshape bodies. Templates are powerful and hard to test; for a Lambda or HTTP proxy integration, prefer to pass the request through unchanged and do the work in code. Use mapping templates for direct AWS service integrations (for example writing to SQS or DynamoDB without a Lambda) where there is no code to put the logic in.
Stages, custom domains and routing
Custom domain
The default https://<api-id>.execute-api.<region>.amazonaws.com URL is fine for tests; production uses a name you
own, with an ACM certificate (certificate management and DNS are on Route 53, DNS and ACM Certificates). For
a Regional custom domain the certificate must be in the same Region as the API (for edge-optimized REST domains
it must be in us-east-1).
In the AWS Management Console
-
In ACM in
us-east-1request or import a certificate forapi.example.comand validate it with DNS. -
In API Gateway → Custom domain names → Create, enter
api.example.com, choose the TLS 1.2 security policy, the Regional endpoint type and the certificate, then Create. -
In API mappings → Configure API mappings map
bookshelf-apiand stageprodto the empty path (orv1). -
Copy the API Gateway domain name and its hosted zone ID. In Route 53 create an A (alias) record
api.example.comthat points to Alias to API Gateway API in the Region. -
Open the API’s Stages and, to force traffic through the custom domain, disable the default endpoint (API settings → Disable default endpoint).
With the AWS CLI
export CERT_ARN=arn:aws:acm:us-east-1:111122223333:certificate/00000000-0000-0000-0000-000000000000
aws apigatewayv2 create-domain-name --domain-name api.example.com \
--domain-name-configurations CertificateArn="$CERT_ARN",EndpointType=REGIONAL,SecurityPolicy=TLS_1_2
aws apigatewayv2 create-api-mapping --domain-name api.example.com --api-id "$API_ID" --stage prod \
--api-mapping-key v1
aws apigatewayv2 get-domain-name --domain-name api.example.com \
--query 'DomainNameConfigurations[0].[ApiGatewayDomainName,HostedZoneId]' --output text
# Optional: only the custom domain is callable
aws apigatewayv2 update-api --api-id "$API_ID" --disable-execute-api-endpoint
Derived from
aws apigatewayv2
create-domain-name, create-api-mapping
and Custom domain names
for HTTP APIs. The two values printed by get-domain-name feed an alias record in Route 53
(aws route53 change-resource-record-sets with AliasTarget). An API mapping attaches an API and stage to a path
under the domain, so https://api.example.com/v1/catalog/books reaches stage prod of bookshelf-api. One domain can serve several APIs by using different keys.
Disabling the default endpoint is important when WAF, throttling or mutual TLS are applied on the custom domain
path only. Mutual TLS is enabled on the domain with --mutual-tls-authentication TruststoreUri=s3://….
Routing rules
API mappings choose the target by path only. Routing rules (introduced in 2025) let a custom domain choose a REST
API stage by request headers and base path, with a priority, so you can send x-version: beta to a staging stage
or split a legacy and a new API behind one domain without redeploying clients:
aws apigatewayv2 update-domain-name --domain-name api.example.com \
--routing-mode ROUTING_RULE_THEN_API_MAPPING
aws apigatewayv2 create-routing-rule --domain-name api.example.com --priority 100 \
--conditions '[{"MatchHeaders":{"AnyOf":[{"Header":"x-version","ValueGlob":"beta"}]}}]' \
--actions '[{"InvokeApi":{"ApiId":"abc123defg","Stage":"beta"}}]'
Derived from Routing rules to
connect API stages to a custom domain name and its
usage page. Rules are
evaluated from the lowest priority value to the highest (leave gaps between priorities), each rule allows up to two
header conditions and one base-path condition and one action, and the target must be in the same account. The routing
mode can be API_MAPPING_ONLY, ROUTING_RULE_THEN_API_MAPPING or ROUTING_RULE_ONLY. Public domains route only to
public APIs and private domains only to private ones; a domain that has API mappings to both REST and HTTP APIs does not
support routing rules. Check the current page for the supported API types before relying on this for HTTP APIs.
Networking and layered topologies
-
Endpoint types. HTTP APIs are Regional. REST APIs are Regional (best when your clients or your own CloudFront distribution are in one Region), edge-optimized (API Gateway puts a managed CloudFront distribution in front; the certificate for a custom domain must be in
us-east-1) or private. -
Back-end reachability. Lambda integrations need no networking. Private HTTP back ends use a VPC link (above); the ALB should be internal and its security group should allow only the VPC link’s security group. A public back end works with plain
HTTP_PROXYURLs, but then anyone can call it directly; protect it with a header secret or mutual TLS, or make it private. -
Layers. A common production shape is
CloudFront → API Gateway → Lambda or VPC link → ALB → ECS: CloudFront gives caching, global entry and WAF for HTTP APIs, API Gateway gives authorization and per-route limits, and the ALB gives health checks and connection to containers. Do not stack gateways for no reason, each hop adds latency and cost; when every request goes to containers on ECS and you need no per-client management, an ALB with authentication rules (see Load Balancers and Gateways) can be enough. -
VPC egress and DNS. API Gateway runs outside your VPC and reaches private resources only through VPC links; it has no static outbound IP address. Back ends that must allow-list the gateway use a VPC link and security groups. The VPC pieces (subnets, security groups, endpoints) are on VPC Networking.
Operations
-
Access logs. Turn them on for every stage (the guide recommends it and Security Hub flags stages without logging). Use JSON format with
$context.requestId,$context.routeKey,$context.status,$context.integrationStatus,$context.integrationErrorMessage,$context.error.messageand$context.authorizer.error, and set a retention period. Log the same request ID in the application to correlate gateway and back-end records. -
REST API execution logs (
ERRORorINFO) additionally record the integration request and response; they need an account-level IAM role for CloudWatch (aws apigateway update-account --patch-operations op=replace,path=/cloudwatchRoleArn,value=<role-arn>) and can contain sensitive data, so keep them atERRORin production. -
Metrics. CloudWatch namespace
AWS/ApiGatewayreportsCount,4xx/4XXError,5xx/5XXError,LatencyandIntegrationLatencyper API and stage (and per route when detailed metrics are on). Alarm on 5xx rate and on the gap betweenLatencyandIntegrationLatency, which is time spent in API Gateway itself (authorizers, mapping). -
Tracing. REST APIs support AWS X-Ray tracing per stage; HTTP APIs do not. For HTTP APIs, start the trace in the back end (the Lambda active tracing, or an OpenTelemetry SDK or the AWS Distro for OpenTelemetry in the container), propagate the incoming
x-amzn-trace-idor W3Ctraceparentheaders, and join traces by the request ID. See Monitoring and Observability and, for the application side, Metrics and Observability: Micrometer, Actuator, and Distributed Tracing. -
Deployments. Keep the definition in code: SAM, CloudFormation or CDK (Infrastructure as Code). An HTTP API with a JWT authorizer, a Lambda route and a stage, as SAM:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
BookshelfApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
Auth:
DefaultAuthorizer: OidcAuthorizer
Authorizers:
OidcAuthorizer:
IdentitySource: $request.header.Authorization
JwtConfiguration:
issuer: https://auth.example.com/
audience: [bookshelf-web]
CorsConfiguration:
AllowOrigins: ["https://www.example.com"]
AllowMethods: [GET, POST]
AllowHeaders: [authorization, content-type]
PingFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: java25
Handler: com.example.bookshelf.edge.PingHandler::handleRequest
CodeUri: target/ping.jar
Events:
Ping:
Type: HttpApi
Properties:
ApiId: !Ref BookshelfApi
Path: /ping
Method: GET
Derived from the SAM
AWS::Serverless::HttpApi reference; sam deploy and sam delete are covered on
AWS Lambda. SAM also creates the Lambda invoke permission for you.
Limits and quotas
Quotas are Region-scoped and change; read the current values with aws service-quotas list-service-quotas
--service-code apigateway and the pages in the references. The numbers below were checked on 2026-10-01.
| Limit | HTTP API | REST API | WebSocket API |
|---|---|---|---|
Account throttle (all types, per Region) |
10,000 requests per second, bucket of 5,000 (lower in some newer Regions); rate adjustable on request |
Same pool |
|
Maximum integration timeout |
30 seconds (fixed) |
29 seconds default; for Regional and private APIs it can be raised at the cost of account throttle quota |
29 seconds per integration call; connection idle timeout 10 minutes; maximum connection duration 2 hours |
Payload size |
10 MB |
10 MB (more with response streaming) |
Messages up to 128 KB (frames of 32 KB) |
Routes or resources per API |
300 routes (adjustable) |
300 resources (adjustable) |
Per-API route quota |
Stages per API |
10 (adjustable) |
10 (adjustable) |
10 (adjustable) |
Authorizers per API |
10 |
10 (Lambda and Cognito) |
10 |
VPC links per account per Region |
10 (adjustable) |
20 (adjustable) |
Not applicable |
Custom domain names per account per Region |
120 public (adjustable); 50 private for REST APIs |
Same pool |
|
Rows for WebSocket values come from the WebSocket pages of the developer guide rather than the HTTP/REST quota pages; confirm them against the current quota page before sizing a design. Other limits that bite in practice: caching is REST only with a 300 second default TTL (0 to 3600) and a 1 MB cached response; the REST API idle connection timeout is 310 seconds; API Gateway control-plane calls (creating deployments, domains, VPC links) have their own, small rate limits that matter for CI pipelines that create and destroy many APIs; and the combined request-line-and-header size is 10,240 bytes on HTTP APIs and 20,480 on REST APIs.
The 29 and 30 second limits decide architecture: a synchronous request that may run longer (report generation, large
exports) should return 202 Accepted and a status URL, push work to SQS or Step Functions
(Lambda: Event-Driven and Serverless Apps), or use REST response streaming.
Pricing model
No prices are stated here; use the API Gateway pricing page and the AWS Pricing Calculator for current figures (checked 2026-10-01).
-
HTTP APIs are billed per million requests received, metered in 512 KB increments of request payload, and cost noticeably less per request than REST APIs. Tiered per-million prices fall as monthly volume grows.
-
REST APIs are billed per million requests received, with the cost of optional features on top: an hourly charge for the cache sized by instance type, data transfer out, and charges for response streaming.
-
WebSocket APIs are billed per million messages (metered in 32 KB increments, up to 128 KB) plus connection minutes.
-
Free tier. New accounts have had an allowance of millions of calls for the first twelve months; AWS has changed how free-tier allowances are granted for new accounts, so read the current terms on the pricing page.
-
Not on the API Gateway line item but part of the bill: data transfer out (EC2 data-transfer rates), the Lambda invocations that the API causes, CloudWatch Logs ingestion for access and execution logs, Lambda authorizer invocations (cache their results), the VPC link’s network interfaces and cross-AZ traffic, AWS PrivateLink and interface endpoint hours for private APIs, WAF web ACL and request charges, ACM public certificates (free) and the Route 53 hosted zone.
-
Control levers. Use an HTTP API unless you need a REST-only feature; cache and CloudFront-cache GET responses; set log retention; cache authorizer results; avoid chatty clients with throttling; delete unused stages, APIs and VPC links (an unused VPC link still holds network interfaces and an unused regional custom domain keeps its certificate tied up).
See Cost Management and Support for budgets and anomaly detection.
Troubleshooting
Start with the access log and the response headers; x-amzn-requestid and x-amz-apigw-id in the response identify
the request for AWS Support. Test the integration directly (call the ALB or invoke the Lambda) to see whether the fault is
in API Gateway or behind it.
| Symptom | Likely causes and fixes |
|---|---|
|
A route or resource that does not exist on a REST API (it answers 403 rather than 404 for missing paths), a missing
API key, the wrong custom-domain mapping, WAF blocking, a resource policy deny, or a private API called from outside its
VPC endpoint. Check |
|
No |
|
Unsigned request, signature for the wrong service or Region, expired temporary credentials, or the caller’s policy
missing |
|
No route matches the method and path; check the stage prefix in the URL and whether the stage name or API mapping key is part of the path. |
|
Stage, route, usage plan or account throttle reached. Look at the |
|
Misconfigured integration, invalid response from a Lambda function (for REST proxy integrations the response must contain
|
|
The back end closed the connection, returned a malformed response, or the VPC link cannot reach it (security group, target group health, wrong listener ARN, TLS server name mismatch). Test from an instance in the same subnets. |
|
Back end unavailable or a VPC link that is |
|
The integration did not answer within the limit (29 or 30 seconds). Make the work asynchronous or faster; a Lambda timeout longer than the API limit does not help the caller. |
Browser shows a CORS error but the request reached the API |
The error response ( |
Back end sees the wrong path |
Private integrations include the stage name in the forwarded path; API mappings strip the mapping key. Use parameter
mapping with |
Lambda is not invoked, |
The function’s resource policy is missing or its |
The official guides are Troubleshoot HTTP APIs and the API Gateway troubleshooting section.
Clean up
An API, its stages and its routes cost nothing while idle, but a custom domain, a VPC link, a cache, a WAF association and the log groups can keep costing. Delete in dependency order:
# Custom domain: mappings first, then the domain, then the Route 53 record
aws apigatewayv2 list-api-mappings --domain-name api.example.com --query 'Items[].ApiMappingId' --output text
aws apigatewayv2 delete-api-mapping --domain-name api.example.com --api-mapping-id "$MAPPING_ID"
aws apigatewayv2 delete-domain-name --domain-name api.example.com
# HTTP and WebSocket APIs delete their routes, integrations, authorizers and stages with them
aws apigatewayv2 delete-api --api-id "$API_ID"
aws apigatewayv2 delete-api --api-id "$WS_ID"
# REST API and usage plan (remove the stage from the plan first)
aws apigateway delete-usage-plan-key --usage-plan-id "$PLAN_ID" --key-id "$KEY_ID"
aws apigateway delete-usage-plan --usage-plan-id "$PLAN_ID"
aws apigateway delete-api-key --api-key "$KEY_ID"
aws apigateway delete-rest-api --rest-api-id "$REST_ID"
# VPC link, then its security group and the ALB ingress rule
aws apigatewayv2 delete-vpc-link --vpc-link-id "$VPC_LINK_ID"
aws ec2 revoke-security-group-ingress --group-id "$ALB_SG" --protocol tcp --port 443 --source-group "$VPCLINK_SG"
aws ec2 delete-security-group --group-id "$VPCLINK_SG"
# Function permissions, logs, and the throwaway Lambda
aws lambda remove-permission --function-name "$FN_NAME" --statement-id apigw-invoke-"$API_ID"
aws logs delete-log-group --log-group-name /aws/apigateway/bookshelf-api
aws lambda delete-function --function-name "$FN_NAME"
Derived from aws apigatewayv2 delete-api,
delete-domain-name,
aws apigateway delete-rest-api and
delete-vpc-link. In the console:
API Gateway → select the API → Actions or Delete; Custom domain names → delete mappings, then the domain;
VPC links → delete; then remove the Route 53 alias record, the ACM certificate if unused, the log group and, in
Lambda, the function. Deleting a custom domain does not delete the DNS record, which would then dangle; and a VPC
link cannot be deleted while integrations still use it. If you used a CloudFormation or SAM stack, run
aws cloudformation delete-stack or sam delete instead.
Related pages
-
API Gateway and Backends for Frontends explains when to use an API gateway or a Backend for Frontend; this page is the AWS implementation.
-
AWS Lambda and Lambda: Event-Driven and Serverless Apps cover the functions behind the API and the HTTP front-end choices (function URL, API Gateway, ALB).
-
Load Balancers and Gateways compares ALB, NLB and the other gateways, and CloudFront and CDN Delivery covers the CDN in front.
-
Identity and Access and the OAuth pages under OAuth Reference explain the tokens and roles used for authorization.
-
VPC Networking and Route 53, DNS and ACM Certificates cover VPC links' network, DNS and certificates.
-
API Management is the Azure counterpart.