Skip to content

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

TypeCoverage
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 assertionThrottle 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
  • Story #23 (API module — Lambda + API GW) deployed to QA
  • Story #29 (CI pipeline) in place so the tfsec check on the Terraform module can run
  • terraform plan shows throttling_rate_limit and throttling_burst_limit set on the POST /query route
  • /healthz route 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

IaC: CI. Integration: manual against QA. Prod throttle values verified at Prod deploy time (Story #28 exit criteria).

Terraform, curl or a simple load script (hey, ab), AWS CLI (aws cloudwatch get-metric-statistics on Lambda invocations).

Terminal window
# Given the QA API Gateway has a burst limit of B on POST /query
# When B+5 requests are sent in rapid succession
for 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"}' &
done
wait
# Then at least one response is 429
Terminal window
# Given the same burst is applied to /healthz
for i in $(seq 1 $((BURST_LIMIT + 5))); do
curl -s -o /dev/null -w "%{http_code}\n" "$QA_API_URL/healthz" &
done
wait
# 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_stage with route_settings per-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.

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.