A fully managed "front door" for APIs — it terminates client requests, authenticates and authorizes them, optionally transforms and validates the payload, invokes a backend integration (Lambda, another AWS service, or an HTTP endpoint), and shapes the response — all without you running or scaling a single server. On the DVA-C02 exam, API Gateway questions almost always hinge on picking the right API type, the right integration mode, or the right authorizer for a described scenario, and spotting the classic proxy-vs-non-proxy and CORS traps.
GET /orders/{id}.dev, prod) with its own configuration (throttling, caching, variables, logging).Nearly every API Gateway question tests one of four things: (1) can you choose REST API vs HTTP API based on described feature/cost/latency requirements, (2) do you understand the proxy vs non-proxy Lambda integration distinction — proxy passes the whole request through untouched, non-proxy needs mapping templates, (3) can you pick the right authorizer for a described identity/auth requirement, and (4) can you diagnose that a CORS error is actually a missing response header from the backend, not just a checkbox in API Gateway.
HTTPS request hits the API's invoke URL — either the default https://{api-id}.execute-api.eu-west-1.amazonaws.com/{stage} or a custom domain — targeting a resource/method (REST) or route (HTTP)
API Gateway resolves which deployed stage config applies — throttling limits, stage variables, caching settings, logging level for that stage
Cognito User Pool authorizer validates a JWT, IAM auth validates a SigV4-signed request, or a Lambda authorizer runs custom logic. Unauthorized requests are rejected here with 401/403 — the backend integration is never invoked
Method/route-level throttling (token bucket: rate + burst) rejects excess traffic with 429. Request validation checks required parameters/headers or a JSON schema and rejects malformed input with 400 — before the integration runs
Lambda proxy (raw event passed as-is) or Lambda non-proxy (request mapping template/VTL transforms the payload first), an HTTP backend, a direct AWS service integration, or a Mock integration
Non-proxy integrations pass the backend response through a response mapping template; proxy integrations return the Lambda-formatted response near-verbatim (status code, headers, body). Caching (REST only) may serve the response from cache within its TTL
Access logs and execution logs stream to CloudWatch Logs; if X-Ray tracing is enabled on the stage, the request/response is captured as a segment in the trace and service map
| DVA-C02 Domain | Where API Gateway Shows Up |
|---|---|
| Domain 1 — Development with AWS Services | The centerpiece — choosing REST vs HTTP API, integration types, mapping templates, request validation, direct AWS service integrations |
| Domain 2 — Security | Authorizer selection (Cognito/IAM/Lambda), API keys/usage plans, resource policies, CORS as a security-adjacent misconfiguration |
| Domain 3 — Deployment | Stages, deployments, canary release deployments, custom domains + base path mapping, stage variables driving which Lambda alias/version is invoked |
| Domain 4 — Troubleshooting and Optimization | Diagnosing 429 throttling errors, 403 authorizer failures, CORS errors, latency issues (caching, integration timeout), reading CloudWatch/X-Ray for root cause |
API Gateway is one of the most consistently tested services on DVA-C02 because it sits at the intersection of all four domains — expect several questions built around it directly, plus many more where it's one component of a larger serverless architecture question.
Access-Control-Allow-Origin response header from the backend, not an API Gateway settingExam appearance probability: HIGH
The configuration surface that shows up repeatedly in scenario questions: how requests are structured, how they reach a backend, who's allowed to call them, and how traffic is controlled.
/orders, /orders/{id}); each resource has one or more Methods (GET/POST/PUT/DELETE/ANY)METHOD /path pair (e.g. GET /orders/{id}), with an optional catch-all $default route/integration{id}), greedy path variables ({proxy+} in REST), and query-string parameters.dev, test, prod) with its own throttling, caching, logging, and stage variablesdev at a DEV alias and prod at a PROD alias without duplicating the API.{"statusCode": ..., "headers": {...}, "body": "..."} (body as a JSON-encoded string) — API Gateway does no transformation. If your function returns a bare object instead of this shape, API Gateway returns a 500 error.A question describing "the Lambda function needs full access to headers, query parameters, and the HTTP method" is testing proxy integration. A question describing "the request body must be transformed into a different schema before it reaches a legacy-style Lambda function, without changing the Lambda function's code" is testing non-proxy integration with mapping templates.
"Reduce operational overhead by removing the Lambda function entirely for a simple write-to-DynamoDB API" → AWS service integration (direct DynamoDB integration), not a Lambda proxy. This is a frequent "LEAST operational overhead" pattern question.
Authorization header) and returns an IAM policy document + optional contextx-api-key header — used for metering/throttling, not for verifying identityCache-Control: max-age=0 — provided the client has the correct IAM permission for cache invalidation, otherwise this header is ignored (an intentional protection against unauthorized cache-busting).Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers)."Enabled CORS in the console, preflight (OPTIONS) succeeds, but the browser still blocks the actual GET/POST request with a CORS error" is testing whether you know the real GET/POST response — typically produced by a Lambda proxy integration — is missing Access-Control-Allow-Origin in its own headers. The fix is in the Lambda function's response, not another look at the API Gateway CORS checkbox.
api.example.com) requires an ACM certificate — regional certificate in the API's region for a Regional custom domain, or in us-east-1 for an Edge-optimized custom domain (used with the CloudFront distribution backing edge-optimized REST APIs)./v1) to a specific API and stage — so api.example.com/v1 can route to API A's prod stage while api.example.com/v2 routes to API B's prod stage, or to a different stage of the same API entirely.$input.body, $input.path()), headers, query/path parameters, and stage variables to build the outbound payload.Count, 4XXError, 5XXError, Latency, IntegrationLatency, and CacheHitCount/CacheMissCount.Latency (total time including API Gateway overhead) from IntegrationLatency (time spent waiting on the backend) is the key to diagnosing whether a slow API is an API Gateway configuration issue or a slow backend.Requirement → Keywords → Expected Answer → why every distractor fails.
HTTP API with a JWT authorizer
| Distractor | Why it's wrong |
|---|---|
| REST API with Cognito User Pool authorizer | Functionally works but costs more and adds latency versus HTTP API — wrong when the requirement explicitly asks for lowest cost/latency and none of the REST-only features (usage plans, caching, canary) are needed |
| Application Load Balancer with Lambda targets | ALB can invoke Lambda but lacks native JWT/OIDC authorizer support and API-specific features like route-based path matching the way API Gateway offers |
| REST API with a Lambda authorizer | Adds an unnecessary Lambda invocation and REST API's higher cost when HTTP API's built-in JWT authorizer already covers standards-based JWT validation natively |
REST API
| Distractor | Why it's wrong |
|---|---|
| HTTP API | Does not support usage plans, API keys, response caching, or full JSON Schema request validation — all explicitly required here |
| ALB + Lambda | No native concept of API keys, usage plans, or response caching at all |
| CloudFront + Lambda@Edge | CloudFront caches, but has no usage-plan/API-key/request-validation concept — solving a different layer of the problem |
Lambda proxy integration
| Distractor | Why it's wrong |
|---|---|
| Lambda non-proxy integration | Would require building and maintaining a mapping template to manually forward every header/param the function needs — unnecessary extra work when proxy already passes everything |
| HTTP integration | Routes to an HTTP endpoint, not a Lambda function invocation |
| Mock integration | Never calls a backend at all |
Lambda non-proxy (custom) integration with a request mapping template (VTL)
| Distractor | Why it's wrong |
|---|---|
| Lambda proxy integration | Passes the request through unmodified — the transformation the requirement asks for would have to happen inside the function, contradicting "without changing app code" |
| Add a second Lambda function as a transformation layer | Works but is unnecessary extra infrastructure and cost when a mapping template does this natively at the gateway |
| Request validation | Rejects malformed requests; it doesn't transform valid ones into a different shape |
IAM authorization (SigV4-signed requests)
| Distractor | Why it's wrong |
|---|---|
| Cognito User Pool authorizer | Built for end-user identity via a Cognito user directory, not for AWS-native service-to-service calls that already carry IAM credentials |
| API keys | API keys identify a caller for throttling/billing, they are not an authentication mechanism and don't verify AWS identity |
| Lambda authorizer | Adds custom logic and a Lambda invocation to solve something IAM auth already handles natively via existing IAM roles/policies |
Lambda authorizer — TOKEN type
| Distractor | Why it's wrong |
|---|---|
| Cognito User Pool authorizer | Only validates tokens issued by a Cognito User Pool (or a Cognito-federated identity), not an arbitrary third-party IdP's JWT structure |
| Lambda authorizer — REQUEST type | Overkill when the decision only needs the bearer token itself, not the full request context — token type is the more precise/simpler fit |
| IAM authorization | Validates AWS SigV4 signatures, not arbitrary third-party JWTs |
Lambda authorizer — REQUEST type
| Distractor | Why it's wrong |
|---|---|
| Lambda authorizer — TOKEN type | Only receives the bearer token itself — cannot see headers, query params, or source IP needed for this decision |
| Cognito User Pool authorizer | Purpose-built for validating a Cognito JWT, not for arbitrary multi-input custom logic |
| Resource policy | Can restrict by source IP/VPC/AWS account at the API level, but can't run custom application logic combining multiple request attributes |
Add Access-Control-Allow-Origin (and related) headers to the Lambda function's actual response
| Distractor | Why it's wrong |
|---|---|
| Re-enable CORS in the API Gateway console again | Enabling CORS only configures the auto-generated OPTIONS preflight response — it does not retroactively add headers to a Lambda proxy integration's real GET/POST response |
| Switch to a non-proxy integration | Would work if you also build a response mapping template to inject the headers, but is unnecessary rearchitecting versus simply fixing the Lambda response — the proxy model is not itself the problem |
| Move the API behind CloudFront | Does not add missing CORS headers to the origin response; the browser still evaluates headers from the actual response received |
API keys + usage plans (REST API)
| Distractor | Why it's wrong |
|---|---|
| WAF rate-based rule | Rate-limits by source IP pattern, not by a per-customer identifier tied to a metered quota |
| Account-level throttling only | Applies uniformly across the whole account/region — cannot differentiate limits or quotas between individual customers |
| HTTP API | Has no usage plan / API key concept at all — this requirement alone rules it out |
Enable API Gateway caching on that method (REST API)
| Distractor | Why it's wrong |
|---|---|
| Increase Lambda memory/concurrency | Addresses backend compute speed, not the repeated round-trip to the backend for data that hasn't changed |
| Switch to HTTP API for lower baseline latency | HTTP API has slightly lower baseline overhead but has no caching feature at all — doesn't solve "avoid repeated backend calls for unchanged data" as directly as caching does |
| Add a Lambda authorizer cache | Only caches the authorization decision, not the actual response payload |
API Gateway direct (AWS service) integration to DynamoDB
| Distractor | Why it's wrong |
|---|---|
| Lambda proxy integration writing to DynamoDB | Introduces a Lambda function to maintain, patch, and pay for, purely to shuttle a request into DynamoDB — more operational overhead than needed |
| ALB + EC2 application writing to DynamoDB | Requires managing servers/Auto Scaling — far more operational overhead than a direct managed integration |
| Step Functions Express Workflow triggered by API Gateway | Adds orchestration machinery unnecessary for a single simple write |
See the Cognito guide for the full User Pools vs Identity Pools breakdown and JWT structure (ID token vs access token vs refresh token).
PutItem/SendMessage/StartExecution call → service invoked directly using the API Gateway execution role's IAM permissionsMobile/web client authenticates against a Cognito User Pool (hosted UI or SDK), receiving an ID token and access token (JWT)
Client calls an HTTP API endpoint, e.g. GET /orders, with the access token in the Authorization header
HTTP API's JWT authorizer validates the token's signature, issuer, and audience against the User Pool — rejects with 401 if invalid/expired, otherwise passes claims into the request context
Lambda proxy integration invokes a function that reads the caller's sub claim from the request context and queries DynamoDB for that user's orders
X-Ray tracing (enabled on the stage and inside the Lambda function) captures the full trace — API Gateway segment, Lambda segment, DynamoDB subsegment — visualized as a service map; CloudWatch dashboards/alarms watch 5XXError and Latency metrics
This exact pattern — API Gateway (HTTP API) → Lambda → DynamoDB with a Cognito authorizer and X-Ray tracing — is one of the canonical serverless architectures on the exam. See the Cross-Service Architectures guide for the fully worked version alongside four other end-to-end architectures.
| Feature | REST API | HTTP API |
|---|---|---|
| Relative cost | Baseline (higher) | ~70% cheaper for equivalent traffic |
| Latency | Baseline | Lower — fewer processing steps |
| Lambda proxy integration | Yes | Yes |
| Lambda non-proxy integration + VTL mapping templates | Yes | No |
| API keys & usage plans | Yes | No |
| Response caching | Yes | No |
| Request validation (JSON Schema) | Yes, full JSON Schema | Basic parameter checks only |
| Authorizers | Cognito User Pool, IAM, Lambda (token/request) | Native JWT (OIDC/OAuth2), IAM, Lambda (simplified request format) |
| Private APIs (VPC endpoint + resource policy) | Yes | No (VPC Link for private integrations is supported; the API itself can't be made VPC-private the way REST APIs can) |
| Canary release deployments | Yes | No |
| Endpoint types | Edge-optimized, Regional, Private | Regional only |
| AWS WAF integration | Yes — Web ACL attaches directly to the API | No — HTTP APIs aren't a WAF-associable resource; front it with CloudFront and attach the Web ACL there instead |
| Automatic deployments | Manual deployment step required | Optional auto-deploy on change |
If a scenario mentions any of: API keys, usage plans, caching, request body JSON Schema validation, canary deployments, or a private VPC-only API — REST API is required and HTTP API is automatically wrong, regardless of how strongly "lowest cost" is emphasized elsewhere in the question.
| Misconception | Reality |
|---|---|
| "Enabling CORS in API Gateway fixes all CORS errors" | It only configures the auto-generated OPTIONS preflight response. The real GET/POST/etc. response — especially from a Lambda proxy integration — must itself include the CORS headers |
| "Lambda proxy integration passes the response through unmodified" | API Gateway enforces a specific response shape (statusCode/headers/body); a bare returned object causes a 500 error, not a pass-through |
| "Non-proxy integration is simpler because there's less to configure in Lambda" | It shifts complexity to API Gateway — you must write and maintain request AND response VTL mapping templates |
| "HTTP APIs support usage plans and API keys just like REST APIs" | They don't — this is a hard, frequently tested feature gap |
| "Stage variables are just for display/documentation" | They're live configuration values, commonly used to point a stage at a specific Lambda alias/version or a different backend endpoint per environment |
| "Saving a change to a method/integration makes it live immediately" | Nothing takes effect for callers until you create a new Deployment to the target stage |
| "Throttling is a single account-wide number" | It's layered — account, stage, and method/route level — with the most specific configured limit applying (never exceeding the account ceiling) |
| "API keys authenticate the caller" | API keys identify a caller for metering/throttling; they are not proof of identity and provide no real access control on their own |
| "A custom domain automatically knows which API/stage to route to" | You must explicitly create a base path mapping connecting the domain (and optional path) to a specific API and stage |
| "Caching guarantees fresh data disappears instantly on backend change" | Cached responses persist until TTL expiry (default 300s, up to 3600s) or an explicit cache flush/invalidation |
Three self-contained exercises, all in eu-west-1, using AWS CLI v2. Exercise 1 builds an HTTP API with a Lambda proxy integration. Exercise 2 rebuilds the same conceptual endpoint on a REST API with a non-proxy integration and a real VTL mapping template, so you can see the proxy-vs-non-proxy trap from Components 1.3 happen on your own screen instead of just reading about it. Exercise 3 layers a usage plan and API key onto the REST API and deliberately triggers a 429. Replace <ACCOUNT_ID> with your account ID throughout.
cat > trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role \
--role-name orders-proxy-lambda-role \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \
--role-name orders-proxy-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
CreateRole returns an Arn like arn:aws:iam::<ACCOUNT_ID>:role/orders-proxy-lambda-role; the policy attaches with no output.
statusCode, headers, and a JSON-string body.
cat > index.js <<'EOF'
exports.handler = async (event) => {
const id = event.pathParameters && event.pathParameters.id;
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: id,
status: "CONFIRMED",
source: "lambda-proxy",
rawQueryString: event.rawQueryString || ""
})
};
};
EOF
zip function-proxy.zip index.js
function-proxy.zip is created in the current directory.
This is the proxy contract tested repeatedly on DVA-C02: if this function returned a bare object like { id, status } instead of the statusCode/headers/body shape, API Gateway would not attempt to interpret it — it would return a 500 error to the client. Proxy integrations do zero response transformation.
aws lambda create-function \ --region eu-west-1 \ --function-name orders-proxy-fn \ --runtime nodejs20.x \ --handler index.handler \ --role arn:aws:iam::<ACCOUNT_ID>:role/orders-proxy-lambda-role \ --zip-file fileb://function-proxy.zipcreate-function returns FunctionArn: arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:orders-proxy-fn, State ACTIVE (or Pending briefly).
aws apigatewayv2 create-api \ --region eu-west-1 \ --name orders-http-api \ --protocol-type HTTPReturns an ApiId (e.g. abc123xyz4) and an ApiEndpoint like https://abc123xyz4.execute-api.eu-west-1.amazonaws.com. Save the ApiId — it's needed in every following command.
aws apigatewayv2 create-integration \
--region eu-west-1 \
--api-id <API_ID> \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:orders-proxy-fn \
--payload-format-version 2.0 \
--integration-method POST
aws apigatewayv2 create-route \
--region eu-west-1 \
--api-id <API_ID> \
--route-key "GET /orders/{id}" \
--target integrations/<INTEGRATION_ID>
create-integration returns an IntegrationId to plug into --target as integrations/<INTEGRATION_ID>; create-route returns a RouteId.
aws lambda add-permission \ --region eu-west-1 \ --function-name orders-proxy-fn \ --statement-id apigw-invoke-proxy \ --action lambda:InvokeFunction \ --principal apigateway.amazonaws.com \ --source-arn "arn:aws:execute-api:eu-west-1:<ACCOUNT_ID>:<API_ID>/*/*/orders/*" aws apigatewayv2 create-stage \ --region eu-west-1 \ --api-id <API_ID> \ --stage-name dev \ --auto-deployadd-permission returns a Statement JSON blob; create-stage returns StageName "dev" with AutoDeploy true, so future changes go live without a manual deployment call.
curl -s "https://<API_ID>.execute-api.eu-west-1.amazonaws.com/dev/orders/42?expand=items"{"id":"42","status":"CONFIRMED","source":"lambda-proxy","rawQueryString":"expand=items"} — the whole request (path param AND query string) reached the function untouched, with zero API Gateway configuration to parse them.
Same conceptual GET /orders/{id} endpoint, rebuilt as a REST API with a non-proxy (custom) Lambda integration. This time API Gateway — not the function — is responsible for shaping both the request going in and the response coming out, via VTL mapping templates.
statusCode/headers/body envelope required.
cat > index.js <<'EOF'
exports.handler = async (event) => {
return {
orderId: event.orderId,
status: "CONFIRMED",
filter: event.queryParams ? event.queryParams.expand : null
};
};
EOF
zip function-nonproxy.zip index.js
aws lambda create-function \
--region eu-west-1 \
--function-name orders-nonproxy-fn \
--runtime nodejs20.x \
--handler index.handler \
--role arn:aws:iam::<ACCOUNT_ID>:role/orders-proxy-lambda-role \
--zip-file fileb://function-nonproxy.zip
FunctionArn arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:orders-nonproxy-fn, State ACTIVE. Note the function never sees an "event" shaped like the raw HTTP request — it only sees whatever the request mapping template builds in the next steps.
/orders/{id} resource tree.
aws apigateway create-rest-api \
--region eu-west-1 \
--name orders-rest-api \
--endpoint-configuration types=REGIONAL
# Get the auto-created root resource id ("/")
aws apigateway get-resources --region eu-west-1 --rest-api-id <REST_API_ID>
aws apigateway create-resource \
--region eu-west-1 --rest-api-id <REST_API_ID> \
--parent-id <ROOT_RESOURCE_ID> --path-part orders
aws apigateway create-resource \
--region eu-west-1 --rest-api-id <REST_API_ID> \
--parent-id <ORDERS_RESOURCE_ID> --path-part "{id}"
Two new resources exist: /orders and /orders/{id}, each returned with its own ResourceId to use below.
/orders/{id} (no auth for this lab).
aws apigateway put-method \ --region eu-west-1 --rest-api-id <REST_API_ID> \ --resource-id <ID_RESOURCE_ID> --http-method GET \ --authorization-type NONE \ --request-parameters "method.request.path.id=true,method.request.querystring.expand=false"put-method returns the method definition with httpMethod GET and the declared request parameters.
cat > request-template.vtl <<'EOF'
{
"orderId": "$input.params('id')",
"queryParams": {
#foreach($p in $input.params().querystring.keySet())
"$p": "$util.escapeJavaScript($input.params().querystring.get($p))"#if($foreach.hasNext),#end
#end
}
}
EOF
aws apigateway put-integration \
--region eu-west-1 --rest-api-id <REST_API_ID> \
--resource-id <ID_RESOURCE_ID> --http-method GET \
--type AWS \
--integration-http-method POST \
--uri "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:orders-nonproxy-fn/invocations" \
--request-templates file://request-template-wrapped.json
put-integration returns type AWS (not AWS_PROXY) — confirming this is a custom, mapping-template-driven integration.
--request-templates takes a JSON object mapping content-type → template string (so the .vtl file above must be wrapped as {"application/json": "<escaped template>"} before passing it as request-template-wrapped.json). This extra packaging step is exactly the "more setup" the guide's Components tab warns about for non-proxy — API Gateway, not your function, now owns the request shape.
cat > response-template.vtl <<'EOF'
#set($inputRoot = $input.path('$'))
{
"order": {
"id": "$inputRoot.orderId",
"status": "$inputRoot.status"
},
"source": "lambda-nonproxy-vtl"
}
EOF
aws apigateway put-method-response \
--region eu-west-1 --rest-api-id <REST_API_ID> \
--resource-id <ID_RESOURCE_ID> --http-method GET \
--status-code 200
aws apigateway put-integration-response \
--region eu-west-1 --rest-api-id <REST_API_ID> \
--resource-id <ID_RESOURCE_ID> --http-method GET \
--status-code 200 \
--response-templates file://response-template-wrapped.json
Both calls return 200 with the method/integration response objects echoed back — the pipeline from Lambda's raw return value to the client-facing JSON is now fully defined by API Gateway, not by the function.
aws lambda add-permission \ --region eu-west-1 \ --function-name orders-nonproxy-fn \ --statement-id apigw-invoke-nonproxy \ --action lambda:InvokeFunction \ --principal apigateway.amazonaws.com \ --source-arn "arn:aws:execute-api:eu-west-1:<ACCOUNT_ID>:<REST_API_ID>/*/GET/orders/*" aws apigateway create-deployment \ --region eu-west-1 --rest-api-id <REST_API_ID> \ --stage-name devcreate-deployment returns a DeploymentId — remember Components 1.2: nothing above this step was visible to callers until this deployment happened.
curl -s "https://<REST_API_ID>.execute-api.eu-west-1.amazonaws.com/dev/orders/42?expand=items"{"order":{"id":"42","status":"CONFIRMED"},"source":"lambda-nonproxy-vtl"} — a differently-shaped, gateway-defined response, versus Exercise 1's function-defined response. Same conceptual endpoint, opposite party (function vs. gateway) controlling the contract.
Layer metering and throttling onto the REST API built in Exercise 2, then push past the limit on purpose.
aws apigateway update-method \ --region eu-west-1 --rest-api-id <REST_API_ID> \ --resource-id <ID_RESOURCE_ID> --http-method GET \ --patch-operations op=replace,path=/apiKeyRequired,value=true aws apigateway create-deployment \ --region eu-west-1 --rest-api-id <REST_API_ID> \ --stage-name devapiKeyRequired is now true on the method. A redeploy is mandatory — re-run the earlier curl and confirm it now returns 403 Forbidden with message "Forbidden" (no x-api-key header sent).
aws apigateway create-api-key \ --region eu-west-1 \ --name orders-lab-key --enabled aws apigateway create-usage-plan \ --region eu-west-1 \ --name orders-lab-plan \ --api-stages apiId=<REST_API_ID>,stage=dev \ --throttle burstLimit=2,rateLimit=1 \ --quota limit=1000,period=MONTH aws apigateway create-usage-plan-key \ --region eu-west-1 \ --usage-plan-id <USAGE_PLAN_ID> \ --key-id <API_KEY_ID> \ --key-type API_KEYcreate-api-key returns an id and a value (the actual key string). The plan is set to rateLimit=1 request/sec, burstLimit=2 — intentionally tight so throttling is easy to reproduce.
rateLimit/burstLimit here are the method/stage-level throttle from Components 1.6's token-bucket model — they can only be as generous as the account-level ceiling, never more. This is also the setup for the classic "per-customer quota" exam scenario: a real usage plan would set a realistic rate but the mechanism is identical.
aws apigateway get-api-key \ --region eu-west-1 --api-key <API_KEY_ID> --include-value curl -s -H "x-api-key: <API_KEY_VALUE>" \ "https://<REST_API_ID>.execute-api.eu-west-1.amazonaws.com/dev/orders/42?expand=items"200 OK with the same VTL-shaped body from Exercise 2 — the key identifies/meters the caller but note it did not need to prove any identity, exactly as Components 1.6 describes.
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code}\n" \
-H "x-api-key: <API_KEY_VALUE>" \
"https://<REST_API_ID>.execute-api.eu-west-1.amazonaws.com/dev/orders/42"
done
The first couple of lines print 200 (covered by the burst of 2), then subsequent lines print 429 as the sustained request rate exceeds rateLimit=1/sec — this is API Gateway's own throttling response, returned before the Lambda function is ever invoked.
Click card to flip. Mark right or wrong to track score.
DVA-C02 scenario style, Easy → Specialty. Select an answer to reveal the explanation.