Docs / API Gateway (REST API)

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

Terminal
# 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.

SubfolderRole
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

  1. Router (router.py:55): extracts api_id + stage, fetches the frozen deployment, applies the canary-split decision, builds the RestApiInvocationContext.
  2. Throttle (handlers/throttle.py:51): token bucket per (api_id, stage); default 10000 RPS, 5000 burst. Over-limit returns 429.
  3. Resource matcher (handlers/resource_router.py:68): Werkzeug-based matcher walks the frozen resource tree; ANY matches the 7 HTTP methods; trailing slashes are stripped.
  4. Authorizer (if attached): real Lambda invocation for REQUEST / TOKEN; see the Authorizers section.
  5. API key validation (if apiKeyRequired=true): checks the configured header against the stage's usage-plan associations.
  6. Method request: parameter mapping, JSON-schema validation via the attached model, required-parameter checks.
  7. Integration request: VTL template renders the integration request body; stage-variable substitution into the integration URI.
  8. Integration dispatch: see the integration table below.
  9. Integration response: matches the selectionPattern, applies the response VTL template, sets the method response headers.

Integration types

TypeBehavior
AWS_PROXYLambda 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).
AWSDirect 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).
HTTPReal requests call to the configured backend URI. Method, headers, query string, body all forwarded.
HTTP_PROXYPass-through to the backend with multi-value header joining; response status, headers, and body preserved verbatim.
MOCKReturns 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

Terminal
$ 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:

Authorizers

TypeBehavior
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_POOLSStub. Any non-empty value in the configured header is accepted. JWT signature and expiration are not verified.
AWS_IAMNot implemented. SigV4 is not verified at the method boundary.
Terminal
$ 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.

Terminal
$ 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

FeatureNotes
REST API CRUDCreateRestApi, resources, methods, integrations, models, request validators, gateway responses, all via moto.
Deployments + stagesDeployments freeze the API; canary percentTraffic splits real traffic; stage variables substituted into URI and credentials.
IntegrationsAWS_PROXY, AWS, HTTP, HTTP_PROXY, MOCK.
VTL templatesReal airspeed-backed evaluator with $context, $input, $stageVariables, $util.
Request validationJSON-Schema via jsonschema; required-parameter checks.
AuthorizersREQUEST + TOKEN Lambda authorizers really run.
API keysReal header validation against usage-plan stage associations.
Per-stage throttlingToken bucket per (api_id, stage); default 10000/5000.
Gateway responsesCustom PutGatewayResponse applied to error paths (4XX_DEFAULT, 5XX_DEFAULT, MISSING_AUTHENTICATION_TOKEN, ...).
Custom domainsCreateDomainName + CreateBasePathMapping store entries and look up Route 53 hosted zones.

Configuration

VariableDefaultPurpose
PROVIDER_OVERRIDE_APIGATEWAYnext_gennext_gen enables the real HTTP runtime. legacy falls back to pure-moto (loses VTL, authorizers, real dispatch).
DISABLE_CUSTOM_CORS_APIGATEWAY0Set to 1 to disable LocalEmu's custom CORS preflight handling at the gateway level. Defined at config.py:787.

Integration points

ServiceHow it touches API Gateway
LambdaAWS_PROXY and AWS integrations invoke target functions synchronously with the standard proxy event shape.
S3, DynamoDB, Kinesis, Step Functions, EventBridge, SQS, SSMAWS-type integrations route to these services directly with auto-injected X-Amz-Target headers where needed.
Route 53CreateDomainName looks up a matching hosted zone.
CloudFormationAWS::ApiGateway::RestApi, Resource, Method, Integration, Deployment, Stage, UsagePlan, UsagePlanKey, ApiKey, Authorizer, DomainName, BasePathMapping all wire through this provider.
CloudTrailEvery 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

See also

For HTTP APIs and WebSocket APIs, see API Gateway v2.