Rate Limits & Errors
Rate Limits
The StackFlow API enforces rate limits at two layers: API Gateway throttling (account-wide) and per-user application-level limits.
| Layer | Limit | Scope |
|---|---|---|
| API Gateway burst | 5,000 req/s | Account-wide |
| API Gateway steady-state | 5,000 req/s | Account-wide |
| Standard endpoints | 300 req/min | Per authenticated user |
| Write operations (POST/PATCH/DELETE) | 60 req/min | Per authenticated user |
| AI endpoints | 60 req/min | Per authenticated user |
| Bulk operations | 10 req/min | Per authenticated user |
| Search endpoints | 120 req/min | Per authenticated user |
Service Accounts: Service accounts with the
service_account role have 10x higher rate limits. Contact support to provision a service account for high-volume integrations.
Throttle Headers
Every API response includes rate limit headers so you can proactively manage your quota:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1716134460
X-RateLimit-Window: 60
Retry-After: 23 # Only present on 429 responses
HTTP Status Codes
| Status | Meaning | Common Cause |
|---|---|---|
| 200 OK | Success | GET, PATCH success |
| 201 Created | Resource created | POST success |
| 204 No Content | Success, no body | DELETE success |
| 400 Bad Request | Invalid input | Missing required fields, invalid enum values |
| 401 Unauthorized | Authentication failed | Missing/expired/invalid token |
| 403 Forbidden | Authorization failed | Insufficient role, tenant mismatch |
| 404 Not Found | Resource does not exist | Invalid ID, deleted record |
| 409 Conflict | State conflict | Invalid state transition, duplicate record |
| 422 Unprocessable Entity | Validation failed | Business rule violation |
| 429 Too Many Requests | Rate limit exceeded | See Retry-After header |
| 500 Internal Server Error | Server error | Lambda error, unexpected exception |
| 503 Service Unavailable | Downstream unavailable | Aurora failover, Neptune timeout |
Error Response Format
All error responses follow this consistent structure:
{
"error": "Human-readable error message",
"code": "MACHINE_READABLE_ERROR_CODE",
"requestId": "7f3a1c2d-8e9b-4f0a-b1c2-d3e4f5a6b7c8",
"details": {
"field": "priority",
"issue": "must be one of P1, P2, P3, P4"
}
}
The requestId maps directly to the CloudWatch Logs request ID for the Lambda invocation. Include it when opening a support ticket.
Error Code Reference
| Code | HTTP | Description |
|---|---|---|
TOKEN_MISSING | 401 | Authorization header absent |
TOKEN_EXPIRED | 401 | JWT token has expired |
TOKEN_INVALID | 401 | JWT signature or format invalid |
WRONG_TOKEN_TYPE | 403 | Access token used instead of ID token |
INSUFFICIENT_ROLE | 403 | User role lacks required permission |
TENANT_MISMATCH | 403 | Resource belongs to different tenant |
RESOURCE_NOT_FOUND | 404 | Record does not exist or was deleted |
VALIDATION_ERROR | 400 | Request body failed validation |
INVALID_STATE_TRANSITION | 409 | State machine transition not allowed |
RATE_LIMIT_EXCEEDED | 429 | Per-user rate limit reached |
THROTTLED | 429 | API Gateway burst limit reached |
INTERNAL_ERROR | 500 | Unexpected Lambda error |
DATABASE_ERROR | 503 | Aurora or Neptune unreachable |
AI_MODEL_ERROR | 503 | Bedrock API error or model throttling |
Retry Strategies
Implement exponential backoff with jitter for robust API integrations:
import time
import random
import requests
def api_call_with_retry(url, headers, method="GET", body=None, max_retries=5):
retryable_codes = {429, 500, 503}
for attempt in range(max_retries):
try:
resp = requests.request(method, url, headers=headers, json=body, timeout=30)
if resp.status_code not in retryable_codes:
return resp
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
# Exponential backoff with jitter
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(min(wait, 60))
except requests.exceptions.Timeout:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
return resp
Do not retry 400/401/403/404 errors. These are client errors that will not resolve with retries. Fix the request before retrying. Only retry 429, 500, and 503 responses.