> ## Documentation Index
> Fetch the complete documentation index at: https://qwady.wiki/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limits

> Per-tier rate limits and quota enforcement

## Limits by tier

| Tier     | Requests/min | Requests/month | Max perPage |
| -------- | ------------ | -------------- | ----------- |
| Free     | 10           | 100            | 10          |
| Starter  | 30           | 5,000          | 50          |
| Pro      | 60           | 25,000         | 500         |
| Business | 120          | 100,000        | 500         |

The per-minute figure is a **burst ceiling for your key**, not a reserved throughput
guarantee. Each tier's limit is fully reachable on its own, but authenticated traffic
across all keys shares a platform-wide budget, so sustained parallel bursts at the top
of the range can return `503` before you reach your own limit. If you need sustained
high throughput rather than headroom for spikes, get in touch before you build against
it — see [Handling 503](#handling-503) below.

## Response headers

Metered `/v1` responses include rate-limit and quota headers. The public `/v1/health` endpoint is outside this middleware chain.

```
X-RateLimit-Limit: 60
X-Quota-Limit: 25000
X-Quota-Used: 142
X-Quota-Remaining: 24858
X-Quota-Overage-Limit: 50000
X-Request-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

| Header                  | Description                                                |
| ----------------------- | ---------------------------------------------------------- |
| `X-RateLimit-Limit`     | Max requests per minute for your tier                      |
| `X-Quota-Limit`         | Included quota for the active quota window                 |
| `X-Quota-Used`          | Requests used in the active quota window                   |
| `X-Quota-Remaining`     | Requests remaining in base quota (can be 0 during overage) |
| `X-Quota-Overage-Limit` | Hard limit including overage headroom (paid tiers only)    |
| `X-Request-Id`          | Unique request ID for log correlation                      |

`X-Quota-Overage-Limit` is only present for paid tiers. When `X-Quota-Used` exceeds `X-Quota-Limit`, you are in the overage zone: requests continue but incur per-request charges. Requests are blocked at the hard limit. See [Pricing](/borough/tiers-and-pricing#overage-billing) for overage rates.

Free anonymous quotas reset on the 1st of each calendar month. Paid customer quotas and overage counters follow the active Polar subscription billing period, so usage aligns with the billing invoice rather than a calendar-month boundary.

## Handling 429 responses

When you exceed the rate limit, the API returns a `429` status with a `Retry-After` header:

```json theme={null}
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit of 60 requests per minute exceeded. Retry after 60 seconds.",
    "status": 429,
    "retryAfter": 60
  }
}
```

The wait time (in seconds) is exposed both in the `Retry-After` response header and as `error.retryAfter` in the JSON body. The SDK reads it automatically.

**Best practices:**

1. Read the `Retry-After` header and wait before retrying
2. Implement exponential backoff for repeated 429s
3. Use the SDK, which handles retries automatically

## Handling 503

A `503` with code `UPSTREAM_ERROR` means Borough could not verify your key against its
billing provider — usually because the shared verification budget is momentarily
saturated, occasionally because the provider is degraded. It is explicitly **retryable**
and says nothing about your key being invalid, which is why it is a `503` and not a `401`.

```json theme={null}
{
  "error": {
    "code": "UPSTREAM_ERROR",
    "status": 503
  }
}
```

Retry with exponential backoff and jitter. Do not re-issue or rotate your key in response
to a `503` — the key is fine. The SDK retries this automatically. If you see it steadily
rather than in bursts, tell us: that is a capacity signal on our side, not a bug in yours.

## Quota exceeded

Paid tiers have overage headroom: requests continue past the base quota and are billed at your tier's overage rate, up to the overage cap. Once the hard limit is reached:

```json theme={null}
{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly quota of 5000 requests plus overage cap exceeded. Resets at the end of the current billing period (2026-06-02T12:00:00.000Z).",
    "status": 429
  }
}
```

Free-tier users are blocked at 100 requests with no overage. The response also carries a `quotaContext` block (and `X-Quota-Tier` / `X-Quota-Upgrade-URL` headers) so you can prompt an upgrade at the moment you hit the limit:

```json theme={null}
{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly quota of 100 requests exceeded. Resets on the 1st of next month.",
    "status": 429,
    "quotaContext": {
      "currentTier": "FREE",
      "currentQuota": 100,
      "nextTier": "STARTER",
      "nextTierQuota": 5000,
      "upgradeUrl": "https://buy.polar.sh/..."
    }
  }
}
```

`nextTier` and `upgradeUrl` are `null` once you are on the top tier (Business).

## Checking your usage

Read the `X-Quota-*` response headers on any API response, or call `GET /v1/usage` for a full breakdown without spending a request:

```json theme={null}
{
  "data": {
    "tier": "STARTER",
    "quota": 5000,
    "used": 1234,
    "remaining": 3766,
    "overageLimit": 15000,
    "percentUsed": 25,
    "periodStart": "2026-07-01T00:00:00.000Z",
    "periodEnd": "2026-08-01T00:00:00.000Z"
  },
  "meta": { "dataAge": null, "source": "live" }
}
```

`GET /v1/usage` is authenticated and rate-limited but does **not** count against your monthly quota. The full machine-readable API spec is served at [`GET /v1/openapi.json`](https://borough.qwady.app/v1/openapi.json) for import into Postman, Bruno, or code generators.
