Rate Limits

Rate Limits

BillerAPI enforces rate limits to ensure fair usage and platform stability. A default limit covers the API data plane, with a few sensitive auth and security endpoints carrying tighter per-route limits. All limits are applied per API key, so your traffic is never affected by other developers.

The limits

ScopeLimitWindow
Default (all data-plane endpoints, per API key)100 requestsPer 60 seconds
Sensitive auth/security endpoints (sign-in, password reset, email verification, sign-up, etc.)5–10 requestsPer 60 seconds

Each window is a fixed 60-second window that resets once it elapses. The default 100/60s limit applies to the API data plane and is not split by read vs. write. A small set of sensitive authentication and security endpoints (such as sign-in, password reset, and email verification) carry tighter per-route limits to protect your account. The limits are the same in sandbox and production.

Requests are counted per API key (your client ID). Requests that arrive without credentials are counted per source IP address instead. If you need a higher limit, contact us at support@billerapi.com.

Rate limit headers

Every response — not just 429s — carries your current budget so you can throttle yourself before you ever hit the limit:

HeaderMeaning
X-RateLimit-LimitMaximum requests allowed in the window (currently 100).
X-RateLimit-RemainingRequests you have left in the current window.
X-RateLimit-ResetSeconds until the current window resets and your budget refills.
Retry-AfterOnly on a 429 — seconds to wait before retrying.

A successful response looks like this:

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 42
Content-Type: application/json

When you exceed the limit

The API returns 429 Too Many Requests with the RATE_LIMITED error code. The seconds to wait before retrying are surfaced two ways: the Retry-After response header and a numeric retry_after field in the JSON error body. Prefer the body field; fall back to the header.

HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{
"error_code": "RATE_LIMITED",
"error_type": "rate_limit",
"error_message": "ThrottlerException: Too Many Requests",
"hint": "You have exceeded the allowed request rate. Back off and retry after the window in the retry_after field / Retry-After header.",
"docs_url": "https://docs.billerapi.com/errors/RATE_LIMITED",
"request_id": "d94f5e2a-8c3b-4f1e-9a7d-6b2c1e0f8a34",
"retry_after": 42
}

Quote the request_id in any support request. See Error Handling for the full error envelope.

Backoff strategy

When you get a 429, wait the number of seconds in retry_after (or the Retry-After header) before retrying. If neither is present, fall back to exponential backoff with jitter. Never retry immediately.

Respect retry_after, with exponential-backoff fallback
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;
    }

    if (attempt === maxRetries) {
      throw new Error('Max retries exceeded');
    }

    // Prefer the retry_after body field, then the Retry-After header,
    // then exponential backoff.
    let delaySeconds;
    try {
      const body = await response.clone().json();
      delaySeconds = typeof body.retry_after === 'number' ? body.retry_after : undefined;
    } catch {
      delaySeconds = undefined;
    }
    if (delaySeconds === undefined) {
      const header = response.headers.get('Retry-After');
      delaySeconds = header ? parseInt(header, 10) : Math.min(Math.pow(2, attempt), 30);
    }

    // Add jitter to prevent thundering herd.
    const jitter = Math.random() * 1000;
    await new Promise(resolve => setTimeout(resolve, delaySeconds * 1000 + jitter));
  }
}

Best practices

  • 1.Watch the headers — read X-RateLimit-Remaining on every response and slow down as it approaches zero, rather than waiting for a 429.
  • 2.Use webhooks instead of polling — subscribe to events like bill.created instead of polling for new bills.
  • 3.Cache and batch — biller metadata and account details change infrequently; cache them, and fetch multiple records in a single paginated request instead of one-by-one.
  • 4.Always back off — never retry immediately after a 429. Wait at least the retry_after duration.

Related

Was this page helpful?