QA plan: API Gateway route-level throttle on POST /query (#65)
Story: API Gateway route-level throttle on POST /query (#65)
Capability: Cost guardrails and observability (#31)
ADRs: RAG004 — HTTP API; RAG009 — /query is the authenticated, cost-bearing route
Test scope
Section titled “Test scope”| Type | Coverage |
|---|---|
| IaC (plan review) | Terraform sets throttling_burst_limit and throttling_rate_limit on the POST /query route stage settings |
| IaC (plan review) | /healthz route has NO throttle override (exempt per security per-feature requirements) |
| Static assertion | Throttle values are documented per environment (QA and Prod) in the runbook or cost.md |
| Integration (manual, QA) | Driving requests above the burst limit returns 429 responses from API Gateway (not from the Lambda) |
| Integration (manual, QA) | Driving requests against /healthz above the query throttle rate does not return 429 |
Entry criteria
Section titled “Entry criteria”- Story #23 (API module — Lambda + API GW) deployed to QA
- Story #29 (CI pipeline) in place so the
tfseccheck on the Terraform module can run
Exit criteria
Section titled “Exit criteria”terraform planshowsthrottling_rate_limitandthrottling_burst_limitset on thePOST /queryroute/healthzroute has no throttle override in the plan- Integration: sending N+1 requests where N is the burst limit within one second returns at least one 429 on
/query - 429 response body and headers come from API Gateway (no Lambda invocation for the throttled requests), confirmed by CloudWatch invocation count staying at N
Environments
Section titled “Environments”IaC: CI. Integration: manual against QA. Prod throttle values verified at Prod deploy time (Story #28 exit criteria).
Tooling
Section titled “Tooling”Terraform, curl or a simple load script (hey, ab), AWS CLI (aws cloudwatch get-metric-statistics on Lambda invocations).
How tests are written
Section titled “How tests are written”# Given the QA API Gateway has a burst limit of B on POST /query# When B+5 requests are sent in rapid successionfor i in $(seq 1 $((BURST_LIMIT + 5))); do curl -s -o /dev/null -w "%{http_code}\n" \ -X POST "$QA_API_URL/query" \ -H "Authorization: Bearer $TEST_TOKEN" \ -H "Content-Type: application/json" \ -d '{"question":"test"}' &donewait# Then at least one response is 429# Given the same burst is applied to /healthzfor i in $(seq 1 $((BURST_LIMIT + 5))); do curl -s -o /dev/null -w "%{http_code}\n" "$QA_API_URL/healthz" &donewait# Then all responses are 200 (no throttling on healthz)- HTTP API throttle is applied at the stage level by default; confirming it is route-specific (POST /query only) requires checking the route settings, not just the stage settings. Verify the Terraform resource is
aws_apigatewayv2_stagewithroute_settingsper-route, not a blanket stage throttle. - The per-key lifetime request quota (RAG009) and this throttle are independent controls; the throttle fires before the authorizer on burst, which is the intended behavior for cost defense.
Capability acceptance criteria link
Section titled “Capability acceptance criteria link”Security per-feature requirements (#65): /query throttle at documented rate; /healthz exempt. Security assessment control “Route-level rate limit”: POST /query carries a route-level throttle, and ANY /{proxy+} is exempt. Exit criteria directly verify both.
© 2026 Benjamin Arunski