Skip to main content

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:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window (e.g. 60).
X-RateLimit-RemainingNumber of requests remaining before hitting the limit.
Retry-AfterSeconds 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-Remaining before making requests to proactively avoid hitting the limit.
  • On a 429 response, wait for the number of seconds in the Retry-After header before retrying.
  • For batch operations, use the POST /v1/batches endpoint 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');
}