Backwork

Rate limits

Per-minute limits for each pack, the rate-limit headers, and how to handle a 429

Each organization has one per-minute request limit, set by its pack. Every API key and OAuth token in the organization shares it. For prices and credits, see Pricing and credits.

PackRequests per minute
Credits60
Build300
Scale1,000

An organization with only its starter credits gets the Credits limit.

How the limit is counted

  • The window is a sliding one minute, not a calendar minute.
  • A request uses as much of the limit as it costs in credits. Most requests cost 1; some cost up to 8. Pricing and credits lists each endpoint. A call that counts as 6 uses 6 of the minute's limit.
  • If the limiter's shared store is briefly unavailable, Backwork falls back to half the limit, and never less than 10 requests a minute, until the store is back. The headers show the limit in force.
  • The sandbox has its own limit: 60 requests a minute for each test key.

Rate-limit headers

Responses carry the organization's current window:

HeaderDescription
X-RateLimit-LimitThe per-minute limit that applies to this request
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterOn a 429 only: seconds to wait before retrying
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 245
X-RateLimit-Reset: 1701878400

Handling a 429

When a request would go over the limit, the API returns 429 Too Many Requests and does not spend credits:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded",
    "hint": "Wait the number of seconds in the Retry-After header, then retry",
    "docUrl": "https://backworkhealth.com/docs/errors#rate-limit-exceeded",
    "retryable": true
  }
}

Wait the number of seconds in Retry-After, then retry:

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);
    if (response.status !== 429) return response;
    const retryAfter = Number(response.headers.get('Retry-After')) || 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
  }
  throw new Error('Still rate limited after retries');
}

Retry only errors whose retryable field is true. A 402 BILLING_CREDITS_EXHAUSTED is not retryable; see Pricing and credits.

Staying under the limit

  • Batch code lookups with POST /api/v1/codes/batch when you have many codes.
  • Cache responses you reuse. Release decisions from GET /api/v1/releases/{releaseId}/decisions never change for a given releaseId, so cache them for as long as you use that release. Jurisdiction reference data changes rarely; a 24-hour cache is reasonable.
  • Spread bulk jobs across the minute instead of sending them at once.

MCP usage

MCP tool calls use the same limit and the same credits as direct API calls. One tool call can make several API requests, and each is counted at its own cost. MCP traffic appears in the usage data with source mcp and, when available, the tool name.

Last updated on

On this page