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

# Rate limits & quotas

> Read your remaining request budget and handle 429s correctly.

Your API key carries one or more rate limits, each with its own window. Every partner
route — `POST /api/chat/v1`, `POST /api/chat/v2`, `GET /api/chat/v2/stream/{runId}` and
`GET /api/chat/v2/quotas` — is authenticated with the same `x-api-key` header and counts
against those limits.

There are two ways to see where you stand: response headers when a request is rejected,
and the quotas endpoint at any time.

## Rate-limit headers on 429

When a request is rejected we return **429** with the standard
[IETF rate-limit headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/):

| Header | Value |
| - | - |
| `Retry-After` | Seconds to wait before retrying. |
| `RateLimit-Limit` | The limit's ceiling for its window. |
| `RateLimit-Remaining` | Requests left in the current window (`0` on a 429). |
| `RateLimit-Reset` | Seconds until the window resets — the same value as `Retry-After`. |

Both `Retry-After` and `RateLimit-Reset` are **relative seconds**, computed at the moment
of rejection, so you never have to reconcile a timestamp against your own clock.

If your key has several limits, these headers describe only the one limit that caused this
particular 429 — not your whole budget. Use [`/quotas`](#checking-your-quotas) for the full
picture.

> [!NOTE]
>
> These headers appear **only on 429 responses**. Successful responses (200) carry no rate-limit
> headers: with multiple limits in play there is no single limit a success could unambiguously
> describe, and nothing was rejected. Poll `/quotas` instead of scraping headers off successful
> calls.

The body is a JSON envelope:

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after the interval in the Retry-After header."
  }
}
```

### Handling a 429

Respect `Retry-After` rather than retrying on a fixed schedule:

```javascript theme={null}
async function callWithRetry(request, maxAttempts = 4) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await request();
    if (response.status !== 429) return response;

    const retryAfter = Number(response.headers.get("Retry-After")) || 1;
    if (attempt === maxAttempts) return response;

    await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
  }
}
```

> [!WARNING]
>
> A 401 is not a rate-limit signal — it means the key is missing or invalid. Retrying will not help;
> check the `x-api-key` header.

## Checking your quotas

`GET /api/chat/v2/quotas` returns every limit configured on your key, so you can throttle
before you get rejected.

```bash theme={null}
curl https://www.verahealth.ai/api/chat/v2/quotas \
  -H "x-api-key: YOUR_API_KEY"
```

```json theme={null}
{
  "quotas": [
    {
      "label": "requests-per-minute",
      "unit": "requests",
      "limit": 60,
      "remaining": 58,
      "reset": 1755432000000,
      "duration": 60000
    },
    {
      "label": "requests-per-month",
      "unit": "requests",
      "limit": 500000,
      "remaining": 481233,
      "reset": 1757462400000,
      "duration": 2592000000
    }
  ]
}
```

| Field | Description |
| - | - |
| `label` | The name of the limit as configured for your key. |
| `unit` | Always `requests` — this API counts requests, not tokens. |
| `limit` | The ceiling for this limit's window. |
| `remaining` | Requests left in the current window. |
| `reset` | **Absolute** Unix timestamp in milliseconds at which the current window resets. |
| `duration` | Window length in milliseconds. |

> [!WARNING]
>
> Unlike the 429 headers, `reset` here is an absolute epoch-milliseconds timestamp, not a duration.
> Compute the wait as `reset - Date.now()`.

Limits are **fixed-window**, not calendar-aligned: a window starts at a fixed multiple of
`duration` since the Unix epoch. A 30-day limit therefore does not reset on the 1st of the
month — always read `reset` rather than assuming a calendar boundary.

> [!NOTE]
>
> `/quotas` is authenticated like any other route and consumes **1 request** from each of your
> limits, exactly as an inference call would. Polling every 1–5 minutes is fine; calling it before
> every request is not.

## Next steps

> [card] [Quickstart](https://docs.verahealth.ai/docs/quickstart.md)
>
> Make your first call to the Chat API.

> [card] [Reconnecting a stream](https://docs.verahealth.ai/docs/guides/reconnecting-a-stream.md)
>
> Resume a stream after a disconnect instead of re-asking the question.

> [card] [API reference](https://docs.verahealth.ai/api-reference/v2/get-your-rate-limit-quotas.md)
>
> Endpoint schemas and playgrounds for every route.

> [card] [Support](mailto:support@verahealth.ai)
>
> Email us to raise your limits.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.