Version: 1.0.0
Category: API Patterns
Purpose: Guide for AI agents on handling API rate limits effectively
Rate limiting protects API servers from overload by restricting the number of requests a client can make within a time period. This pattern explains how to detect, handle, and work within rate limits when using Structs APIs.
Rate limits restrict how many API requests you can make:
Rate limit information is provided in HTTP response headers:
{
"headers": {
"X-RateLimit-Limit": "100",
"X-RateLimit-Remaining": "45",
"X-RateLimit-Reset": "1704067200",
"Retry-After": "60"
}
}
Header Meanings:
X-RateLimit-Limit: Maximum requests allowed in the time windowX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resetsRetry-After: Seconds to wait before retrying (when rate limited)429 Too Many Requests: Rate limit exceeded
{
"status": 429,
"headers": {
"Retry-After": "60",
"X-RateLimit-Limit": "100",
"X-RateLimit-Remaining": "0",
"X-RateLimit-Reset": "1704067200"
},
"body": {
"error": "Rate limit exceeded",
"code": 429,
"message": "Too many requests",
"retry_after": 60
}
}
Base Limit: 60 requests/minute
Endpoints:
Base Limit: 100 requests/minute
Endpoints:
Base Limit: 30 requests/minute
Endpoints:
Approach: Monitor rate limit headers and throttle requests proactively
Implementation:
{
"strategy": "monitor-and-throttle",
"steps": [
{
"step": 1,
"action": "Check X-RateLimit-Remaining header after each request"
},
{
"step": 2,
"action": "If remaining < 10, slow down request rate"
},
{
"step": 3,
"action": "Calculate delay: (60 / limit) * 1.1 seconds between requests"
},
{
"step": 4,
"action": "Resume normal rate when remaining > 20"
}
]
}
Example:
Approach: Retry with exponential backoff when rate limited
Implementation:
{
"strategy": "exponential-backoff",
"on429": {
"initialDelay": 1000,
"maxDelay": 60000,
"backoffMultiplier": 2,
"maxRetries": 5,
"useRetryAfter": true
},
"steps": [
{
"step": 1,
"action": "Receive 429 response"
},
{
"step": 2,
"action": "Read Retry-After header (or use initial delay)"
},
{
"step": 3,
"action": "Wait for delay period"
},
{
"step": 4,
"action": "Retry request"
},
{
"step": 5,
"action": "If still 429, double delay and retry (up to max)"
}
]
}
Example:
Approach: Queue requests and process at rate limit pace
Implementation:
{
"strategy": "request-queuing",
"queue": {
"maxSize": 1000,
"processingRate": "100/minute",
"batchSize": 10
},
"steps": [
{
"step": 1,
"action": "Add requests to queue"
},
{
"step": 2,
"action": "Process queue at rate limit pace"
},
{
"step": 3,
"action": "Monitor rate limit headers"
},
{
"step": 4,
"action": "Adjust processing rate based on remaining"
}
]
}
Benefits:
Do:
{
"afterEachRequest": {
"check": "response.headers.X-RateLimit-Remaining",
"action": "Adjust request rate if low"
}
}
Don’t:
Do:
{
"on429": {
"wait": "response.headers.Retry-After",
"unit": "seconds"
}
}
Don’t:
Do:
{
"throttling": {
"enabled": true,
"rate": "90% of limit",
"monitor": "X-RateLimit-Remaining"
}
}
Don’t:
Do:
{
"caching": {
"enabled": true,
"ttl": 300,
"keys": ["player-{id}", "guild-{id}"]
}
}
Benefits:
Do:
{
"streaming": {
"use": "GRASS/NATS",
"subjects": ["structs.player.*", "structs.planet.*"],
"benefit": "No rate limits on streaming"
}
}
Benefits:
Pattern:
{
"errorHandling": {
"429": {
"action": "retry-with-backoff",
"strategy": "exponential-backoff",
"useRetryAfter": true,
"maxRetries": 5,
"onMaxRetries": "queue-for-later"
}
}
}
Implementation:
Retry-After headerPattern:
{
"headerMonitoring": {
"checkAfterEachRequest": true,
"headers": [
"X-RateLimit-Remaining",
"X-RateLimit-Reset"
],
"actions": {
"remaining < 10": "throttle-requests",
"remaining = 0": "stop-requests-until-reset"
}
}
}
{
"implementation": "simple-throttling",
"code": {
"javascript": "const delay = (60 / rateLimit) * 1000; // ms\n\nasync function makeRequest(url) {\n await new Promise(resolve => setTimeout(resolve, delay));\n return fetch(url);\n}"
},
"description": "Simple fixed delay between requests"
}
{
"implementation": "adaptive-throttling",
"code": {
"javascript": "let remaining = 100;\n\nasync function makeRequest(url) {\n if (remaining < 10) {\n const delay = (60 / rateLimit) * 2 * 1000; // Double delay\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n \n const response = await fetch(url);\n remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));\n return response;\n}"
},
"description": "Adjust delay based on remaining requests"
}
{
"implementation": "exponential-backoff",
"code": {
"javascript": "async function makeRequestWithRetry(url, retries = 5) {\n try {\n const response = await fetch(url);\n \n if (response.status === 429) {\n const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;\n await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));\n \n if (retries > 0) {\n return makeRequestWithRetry(url, retries - 1);\n }\n }\n \n return response;\n } catch (error) {\n throw error;\n }\n}"
},
"description": "Retry with exponential backoff on 429"
}
Metrics to Monitor:
Example:
{
"monitoring": {
"metrics": {
"requestsPerMinute": 85,
"rateLimitRemaining": 15,
"rateLimitLimit": 100,
"rateLimitReset": "1704067200",
"429Errors": 2,
"averageDelay": 0.7
},
"alerts": {
"remaining < 10": "warning",
"remaining = 0": "error",
"429Errors > 5": "error"
}
}
}
api/rate-limits.md - Complete rate limit catalogprotocols/error-handling.md - Error handling protocolapi/error-codes.md - Error code definitionsexamples/errors/429-rate-limit.md - Rate limit error exampleprotocols/streaming.md - GRASS/NATS streaming (no rate limits)X-RateLimit-Limit: Maximum requestsX-RateLimit-Remaining: Remaining requestsX-RateLimit-Reset: Reset timestampRetry-After: Seconds to waitPattern Version: 1.0.0 - December 7, 2025