Rate Limits
The WillItTrack API enforces rate limits to ensure fair usage and service stability.
Default Limits
60requests per 60-second window
Applied to all /v1/* routes. The /health endpoint is not rate limited.
How limits are applied
- •Authenticated requests: Limits are tracked per user ID, per endpoint.
- •Anonymous requests: Limits are tracked per IP address, per endpoint.
- •Windows are computed at minute-level granularity and reset automatically.
Response Headers
Every response from a rate-limited endpoint includes these headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed in the current window (e.g. 60). |
| X-RateLimit-Remaining | Number of requests remaining before hitting the limit. |
| Retry-After | Seconds until the current window resets. Only included on 429 responses. |
429 Response
When you exceed the rate limit, the API returns a 429 status with the standard error envelope:
429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 45
{
"error": {
"type": "invalid_request_error",
"code": "rate_limited",
"message": "Rate limit exceeded. Try again in 60 seconds.",
"request_id": "req_550e8400-e29b-41d4-a716"
}
}Best Practices
- •Check
X-RateLimit-Remainingbefore making requests to proactively avoid hitting the limit. - •On a 429 response, wait for the number of seconds in the
Retry-Afterheader before retrying. - •For batch operations, use the
POST /v1/batchesendpoint instead of making individual check requests — it counts as a single API call. - •Implement exponential backoff for retry logic.
Example retry logic
retry.js
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const waitMs = (parseInt(retryAfter || '60', 10)) * 1000;
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}