API Gateway (REST API)
LocalEmu Amazon API Gateway v1 (REST API) implements all 124 operations. CRUD on REST APIs, resources, methods, integrations, models, request validators, gateway responses, authorizers, API keys, usage plans, domain names, base path mappings, and tags rides on moto. Ten ops are LocalEmu-custom and the real work happens inside the execute_api subpackage: every HTTP request that hits an API URL is routed against the frozen deployment, matched against the resource tree, transformed by the VTL evaluator, and dispatched to the integration backend. This is the v1 REST API; the v2 HTTP / WebSocket runtime lives at /docs/apigateway-v2.
Operation-level coverage: see the API Gateway coverage matrix.
Quick start: Lambda proxy integration
# Lambda to serve the API.
$ awsemu lambda create-function --function-name greet \
--runtime python3.12 --handler index.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--zip-file fileb://greet.zip >/dev/null
$ API_ID=$(awsemu apigateway create-rest-api --name demo \
--query id --output text)
$ ROOT_ID=$(awsemu apigateway get-resources --rest-api-id $API_ID \
--query 'items[0].id' --output text)
$ R_ID=$(awsemu apigateway create-resource --rest-api-id $API_ID \
--parent-id $ROOT_ID --path-part '{name}' --query id --output text)
$ awsemu apigateway put-method --rest-api-id $API_ID --resource-id $R_ID \
--http-method GET --authorization-type NONE >/dev/null
$ awsemu apigateway put-integration --rest-api-id $API_ID --resource-id $R_ID \
--http-method GET --type AWS_PROXY --integration-http-method POST \
--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:greet/invocations >/dev/null
$ awsemu apigateway create-deployment --rest-api-id $API_ID \
--stage-name prod >/dev/null
# Real HTTP traffic: a request hitting this URL is routed through the
# execute_api runtime, matched against the frozen deployment, and dispatched
# to the configured Lambda as an AWS_PROXY invocation.
$ curl -s http://localhost:4566/restapis/$API_ID/prod/_user_request_/tarek
{"greeting":"Hello, tarek!"} The localhost URL pattern http://localhost:4566/restapis/<api-id>/<stage>/_user_request_/<path> hits the same router as the production-shaped https://<api-id>.execute-api.<region>.amazonaws.com/<stage>/<path>. Pick whichever your client library makes easier.
Architecture
Code lives at services/apigateway/. The default provider is ApigatewayNextGenProvider at next_gen/provider.py:52, extending the legacy ApigatewayProvider. Provider selection: PROVIDER_OVERRIDE_APIGATEWAY=next_gen (default) or =legacy.
| Subfolder | Role |
|---|---|
legacy/ | Moto-fallback provider; schema validation; custom-domain + base-path-mapping CRUD with Route 53 hosted-zone lookup. |
next_gen/ | Deployment freezing (CreateDeployment snapshots the API at that moment), stage management, gateway-response overrides, TestInvokeMethod. |
next_gen/execute_api/ | The real HTTP runtime: router, handler chain, integration dispatchers, VTL evaluator, authorizers, throttling, API-key validation. |
Request flow
- Router (
router.py:55): extractsapi_id+stage, fetches the frozen deployment, applies the canary-split decision, builds theRestApiInvocationContext. - Throttle (
handlers/throttle.py:51): token bucket per(api_id, stage); default 10000 RPS, 5000 burst. Over-limit returns 429. - Resource matcher (
handlers/resource_router.py:68): Werkzeug-based matcher walks the frozen resource tree;ANYmatches the 7 HTTP methods; trailing slashes are stripped. - Authorizer (if attached): real Lambda invocation for
REQUEST/TOKEN; see the Authorizers section. - API key validation (if
apiKeyRequired=true): checks the configured header against the stage's usage-plan associations. - Method request: parameter mapping, JSON-schema validation via the attached model, required-parameter checks.
- Integration request: VTL template renders the integration request body; stage-variable substitution into the integration URI.
- Integration dispatch: see the integration table below.
- Integration response: matches the
selectionPattern, applies the response VTL template, sets the method response headers.
Integration types
| Type | Behavior |
|---|---|
AWS_PROXY | Lambda proxy. Builds the proxy event (methodArn, headers, multi-value headers, query string, path parameters, stage variables, requestContext); invokes Lambda synchronously; maps statusCode / headers / body / isBase64Encoded back to the response (integrations/aws.py:350-450). |
AWS | Direct AWS-service integration. Parses the integration URI; routes to S3, DynamoDB, Kinesis, Step Functions, EventBridge, SQS, or SSM; auto-adds the right X-Amz-Target for actions that need it (integrations/aws.py:152). |
HTTP | Real requests call to the configured backend URI. Method, headers, query string, body all forwarded. |
HTTP_PROXY | Pass-through to the backend with multi-value header joining; response status, headers, and body preserved verbatim. |
MOCK | Returns the canned integrationResponses[selectionPattern] payload. Useful for CORS preflight responses and contract tests. |
VPC_LINK integrations are not in the active dispatcher registry. PrivateLink-backed integrations cannot be exercised against LocalEmu.
Stages, deployments, and canary
- •CreateDeployment freezes the API. The deployment captures a snapshot of resources, methods, integrations, and request validators. In-flight edits do not affect a live stage until the next deployment.
- •Stage variable substitution is real.
${stageVariables.<name>}in the integration URI orcredentialsARN is resolved at request time and sanitized to alphanumerics plus._-:/(helpers.py). - •Canary deployments split real traffic. Every request rolls a probability against
canarySettings.percentTrafficand is dispatched to the canary deployment when it lands inside the slice (router.py:101-107). - •Throttle: token bucket per
(api_id, stage). Default 10000 RPS / 5000 burst, configurable via the stagemethodSettings. - •Stage caching is not implemented.
cacheClusterEnabled=trueis accepted but no response cache exists; every request hits the backend.
$ awsemu apigateway create-deployment --rest-api-id $API_ID \
--stage-name dev \
--variables fn=greet-dev >/dev/null
# Re-wire the integration to use ${stageVariables.fn} in the Lambda ARN.
$ awsemu apigateway put-integration --rest-api-id $API_ID --resource-id $R_ID \
--http-method GET --type AWS_PROXY --integration-http-method POST \
--uri 'arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:${stageVariables.fn}/invocations' >/dev/null
$ awsemu apigateway create-deployment --rest-api-id $API_ID --stage-name dev >/dev/null
# dev stage substitutes ${stageVariables.fn} -> greet-dev at request time.
$ curl -s http://localhost:4566/restapis/$API_ID/dev/_user_request_/tarek
{"greeting":"Hello from greet-dev"} VTL mapping templates
The Velocity Template Language evaluator at execute_api/template_mapping.py uses the airspeed library. Context variables populated for both request and response mappings:
- •
$context: request-id, identity, stage, resource path, HTTP method, source IP, authorizer claims (when an authorizer is attached). - •
$input:$input.body,$input.json('$.field')(JSON-encoded subtree),$input.path('$.field')(Python object viajsonpath_rw),$input.params('name')across path / query / header. - •
$stageVariables: every variable on the deployed stage. - •
$util: the standard set of helpers including$util.parseJson,$util.base64Encode,$util.base64Decode,$util.urlEncode,$util.urlDecode.
Authorizers
| Type | Behavior |
|---|---|
REQUEST (Lambda) | Real Lambda invocation with the full event shape (methodArn, path, headers, queryStringParameters, pathParameters, stageVariables). Decision honored; the policy context is exposed as $context.authorizer.<key> in downstream VTL. |
TOKEN (Lambda) | Real Lambda invocation with {type, authorizationToken, methodArn}. Token source is configurable (default method.request.header.Authorization). |
COGNITO_USER_POOLS | Stub. Any non-empty value in the configured header is accepted. JWT signature and expiration are not verified. |
AWS_IAM | Not implemented. SigV4 is not verified at the method boundary. |
$ AUTH_ID=$(awsemu apigateway create-authorizer --rest-api-id $API_ID \
--name lambda-token --type TOKEN \
--authorizer-uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:my-authorizer/invocations \
--identity-source method.request.header.Authorization \
--query id --output text)
$ awsemu apigateway update-method --rest-api-id $API_ID --resource-id $R_ID \
--http-method GET --patch-operations \
op=replace,path=/authorizationType,value=CUSTOM \
op=replace,path=/authorizerId,value=$AUTH_ID >/dev/null
$ awsemu apigateway create-deployment --rest-api-id $API_ID --stage-name prod >/dev/null
# Request without a token: 401 (the authorizer Lambda was really invoked).
$ curl -s -o /dev/null -w '%{http_code}\n' \
http://localhost:4566/restapis/$API_ID/prod/_user_request_/tarek
401
$ curl -s -H 'Authorization: Bearer good-token' \
http://localhost:4566/restapis/$API_ID/prod/_user_request_/tarek
{"greeting":"Hello, tarek!"} API keys and usage plans
API key CRUD round-trips through moto. Per-request validation (handlers/api_key_validation.py:15-90) is real: the configured header (apiKeySource=HEADER or AUTHORIZER) is extracted, the key is looked up against the stage's usage-plan associations, and a missing or invalid key returns 403.
Usage-plan throttle and quota are not enforced. Only the per-stage token bucket runs. If your code depends on the quota.limit field cutting the caller off after N requests in a window, that will not happen here.
$ KEY=$(awsemu apigateway create-api-key --enabled --query value --output text)
$ KEY_ID=$(awsemu apigateway get-api-keys --query 'items[0].id' --output text)
$ PLAN_ID=$(awsemu apigateway create-usage-plan --name basic \
--api-stages apiId=$API_ID,stage=prod --query id --output text)
$ awsemu apigateway create-usage-plan-key --usage-plan-id $PLAN_ID \
--key-id $KEY_ID --key-type API_KEY >/dev/null
$ awsemu apigateway update-method --rest-api-id $API_ID --resource-id $R_ID \
--http-method GET --patch-operations op=replace,path=/apiKeyRequired,value=true >/dev/null
$ awsemu apigateway create-deployment --rest-api-id $API_ID --stage-name prod >/dev/null
$ curl -s -o /dev/null -w '%{http_code}\n' \
http://localhost:4566/restapis/$API_ID/prod/_user_request_/tarek
403
$ curl -s -H "x-api-key: $KEY" \
http://localhost:4566/restapis/$API_ID/prod/_user_request_/tarek
{"greeting":"Hello, tarek!"} Features supported
| Feature | Notes |
|---|---|
| REST API CRUD | CreateRestApi, resources, methods, integrations, models, request validators, gateway responses, all via moto. |
| Deployments + stages | Deployments freeze the API; canary percentTraffic splits real traffic; stage variables substituted into URI and credentials. |
| Integrations | AWS_PROXY, AWS, HTTP, HTTP_PROXY, MOCK. |
| VTL templates | Real airspeed-backed evaluator with $context, $input, $stageVariables, $util. |
| Request validation | JSON-Schema via jsonschema; required-parameter checks. |
| Authorizers | REQUEST + TOKEN Lambda authorizers really run. |
| API keys | Real header validation against usage-plan stage associations. |
| Per-stage throttling | Token bucket per (api_id, stage); default 10000/5000. |
| Gateway responses | Custom PutGatewayResponse applied to error paths (4XX_DEFAULT, 5XX_DEFAULT, MISSING_AUTHENTICATION_TOKEN, ...). |
| Custom domains | CreateDomainName + CreateBasePathMapping store entries and look up Route 53 hosted zones. |
Configuration
| Variable | Default | Purpose |
|---|---|---|
PROVIDER_OVERRIDE_APIGATEWAY | next_gen | next_gen enables the real HTTP runtime. legacy falls back to pure-moto (loses VTL, authorizers, real dispatch). |
DISABLE_CUSTOM_CORS_APIGATEWAY | 0 | Set to 1 to disable LocalEmu's custom CORS preflight handling at the gateway level. Defined at config.py:787. |
Integration points
| Service | How it touches API Gateway |
|---|---|
| Lambda | AWS_PROXY and AWS integrations invoke target functions synchronously with the standard proxy event shape. |
| S3, DynamoDB, Kinesis, Step Functions, EventBridge, SQS, SSM | AWS-type integrations route to these services directly with auto-injected X-Amz-Target headers where needed. |
| Route 53 | CreateDomainName looks up a matching hosted zone. |
| CloudFormation | AWS::ApiGateway::RestApi, Resource, Method, Integration, Deployment, Stage, UsagePlan, UsagePlanKey, ApiKey, Authorizer, DomainName, BasePathMapping all wire through this provider. |
| CloudTrail | Every control-plane call is recorded; dataplane HTTP traffic is not. |
End-to-end test
tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration creates a Lambda, builds a REST API with an AWS_PROXY integration on a path-parameter resource, deploys to a stage, sends a real HTTP request via requests, and asserts the proxy-shaped response was parsed and returned correctly.
Known limitations
- •
COGNITO_USER_POOLSauthorizer is a stub. Any non-empty token in the configured header is accepted; JWT signature and expiration are not verified. - •
AWS_IAMauthorizer is not implemented. SigV4 is not verified at the method boundary. - •Usage-plan throttling and quota are not enforced. Only the per-stage token bucket applies.
quota.limitandthrottle.rateLimiton the usage plan are accepted but ignored. - •Stage caching (
cacheClusterEnabled) is not implemented. GET responses are not cached; every request hits the backend. - •Access logs are metadata-only.
accessLogSettingsis stored and validated but no events are written to the configured CloudWatch Logs destination. No$contextsubstitution into a log line. - •Execution logs (
/aws/apigateway/<api-id>/<stage>) are not written. Use Lambda/CloudWatch logs on the integration target instead. - •X-Ray tracing is not implemented.
X-Amzn-Trace-Idis not propagated to integrations and no X-Ray segments are emitted. - •
VPC_LINKintegration is not in the dispatcher registry. PrivateLink-backed integrations cannot be exercised. - •Request validation is JSON only. XML, form-encoded, and other content types are not validated against the attached model.
- •Resource policy is not enforced. The
policyround-trips on the API record but is not evaluated against the caller's principal at request time. - •Endpoint types do not affect routing.
EDGE,REGIONAL, andPRIVATEare all handled identically as regional. - •mTLS (
mutualTlsAuthentication.truststoreUri) is not enforced. The truststore URI is stored, not validated. - •No WAF integration. Web ACL associations are stored, no filtering happens.
- •
GetSdk(SDK generation) is not implemented. - •OpenAPI import via
PutRestApi --mode=importis partial. Round-trip via OpenAPI is not exercised in tests; expect rough edges with extensions and security schemes.
See also
For HTTP APIs and WebSocket APIs, see API Gateway v2.