| Window | Limit |
| Per minute | 60 requests |
| Per day | 5,000 requests |
| Header | Description |
X-RateLimit-Limit | Maximum requests allowed per minute |
X-RateLimit-Remaining | Requests remaining in the current minute window |
X-RateLimit-Reset | Unix timestamp (seconds) when the minute window resets |
X-RateLimit-Day-Limit | Maximum requests allowed per day |
X-RateLimit-Day-Remaining | Requests remaining today |
429 Too Many Requests response:1234567891011{ "error": { "code": "rate_limit_exceeded", "message": "Too many requests. Please retry after the specified time.", "request_id": "req_abc123def456", "details": { "limit_type": "minute", "retry_after": 12 } } }
Retry-After header with the number of seconds to wait before retrying.| Field | Description |
details.limit_type | Which limit was exceeded: minute or day |
details.retry_after | Seconds until you can retry |
Retry-After header | Same value as details.retry_after |
429 or 5xx response, use exponential backoff to retry. This means waiting progressively longer between retries:12345678910111213141516171819202122232425262728293031323334353637383940async function fetchWithRetry( url: string, options: RequestInit, maxRetries = 5 ): Promise<Response> { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options); if (response.ok) return response; // Don't retry client errors (except 429) if (response.status >= 400 && response.status < 500 && response.status !== 429) { return response; } if (attempt === maxRetries) return response; // Exponential backoff with jitter, capped at 30s const baseDelay = Math.min(1000 * Math.pow(2, attempt), 30000); const jitter = Math.random() * 500; await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter)); } throw new Error("Unreachable"); } // Usage const response = await fetchWithRetry( "https://api.v2.dealmachine.com/v1/enrichment/address", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ addresses: [{ full_address: "123 Main St, Austin, TX 78704" }], }), } );
12345678910111213141516171819202122232425262728293031import time import random import requests def fetch_with_retry(url, headers, json_body, max_retries=5): for attempt in range(max_retries + 1): response = requests.post(url, headers=headers, json=json_body) if response.ok: return response # Don't retry client errors (except 429) if 400 <= response.status_code < 500 and response.status_code != 429: return response if attempt == max_retries: return response # Exponential backoff with jitter, capped at 30s base_delay = min(2 ** attempt, 30) jitter = random.uniform(0, 0.5) time.sleep(base_delay + jitter) # Usage response = fetch_with_retry( "https://api.v2.dealmachine.com/v1/enrichment/address", headers={"Authorization": f"Bearer {api_key}"}, json_body={ "addresses": [{"full_address": "123 Main St, Austin, TX 78704"}] }, )
| Do | Don't |
Retry 429 and 5xx responses with exponential backoff | Retry 400 or 401 errors (fix the request instead) |
Check X-RateLimit-Remaining before sending bursts | Ignore rate limit headers and retry blindly |
| Use batch endpoints to reduce request count | Send one item per request when batch is available |
| Cache results locally when possible | Re-fetch data you already have |
| Add jitter to retry delays | Have all clients retry at the exact same time |