# Rate limits & headers

Each API key can make **600 requests per minute** by default, and have **50 requests in flight at once**. The request allowance refills continuously, so a steady 10 requests per second never trips it, and a short burst is fine as long as the minute averages out. Every response tells you where you stand, in headers.

## Response headers

| Header | When | Meaning |
| --- | --- | --- |
| `X-Request-Id` | Every response | Correlation id, the same value as `metadata.request_id`. Send your own `X-Request-Id` (8 to 64 characters from `A-Z a-z 0-9 . _ -`) and it is echoed back, so you can trace a call through your own logs. Quote it if you contact support. |
| `X-Cache` | Every `200` from a data endpoint | `HIT`: served from Onekey's cache (fresh or stale) or replayed for an `Idempotency-Key`. `MISS`: fetched from the source for this call. |
| `X-Credits-Remaining` | A `200` that charged something | Your account balance in credits after this call, for example `997.8`. **Omitted** on free cache hits and on idempotent replays: nothing was charged, so the balance was not re-read. Use `GET /v1/account` when you need it regardless. |
| `X-RateLimit-Limit` | Every response to a request with a valid key, `429` included | Requests per minute this key may make. |
| `X-RateLimit-Remaining` | Same | Requests left in the current minute, never below `0`. |
| `X-RateLimit-Reset` | Same | Seconds until the allowance is fully restored. |
| `X-Concurrency-Limit` | Every response to a request with a valid key, `429` included | How many requests this key may have in flight at once (default 50). Omitted when no cap applies. |
| `X-Concurrency-Remaining` | Same | Slots left, counting the request you just made as in flight. |
| `Retry-After` | `429`, and the two idempotency answers that ask you to wait (`409 IDEMPOTENCY_IN_PROGRESS`, `503 IDEMPOTENCY_STORE_UNAVAILABLE`) | Whole seconds to wait before retrying. Always at least `1`. |
| `X-Idempotent-Replay` | Idempotent replays only | `true`. The body is the one stored for your `Idempotency-Key`; see [Idempotency](https://onekey.dev/docs/idempotency.md). |
| `Cache-Control` | Every `/v1/` response | `private, no-store`: your data is never cached by a CDN or a shared proxy. The one exception is the public [`/v1/status`](https://onekey.dev/docs/status.md), which is `public, max-age=30`. |

A fresh fetch looks like this (dates and other standard headers left out):

```bash
curl -i "https://api.onekey.dev/v1/github/profile?username=torvalds" -H "x-api-key: YOUR_API_KEY"
# HTTP/1.1 200 OK
# x-request-id: 42261c5943a7451ea87cb6f62933da25
# x-cache: MISS
# x-credits-remaining: 832.8
# x-ratelimit-limit: 600
# x-ratelimit-remaining: 599
# x-ratelimit-reset: 1
# x-concurrency-limit: 50
# x-concurrency-remaining: 49
# cache-control: private, no-store
```

The same call a moment later is a cache hit: `x-cache: HIT`, no `x-credits-remaining`, and `metadata.credits_charged` is `0`.

## When you go over

You get `429 RATE_LIMITED` with a `Retry-After` header. It costs nothing.

```bash
curl -i "https://api.onekey.dev/v1/account" -H "x-api-key: YOUR_API_KEY"
# HTTP/1.1 429 Too Many Requests
# retry-after: 3
# x-ratelimit-limit: 600
# x-ratelimit-remaining: 0
# x-ratelimit-reset: 3
```

```json
{
  "success": false,
  "data": null,
  "metadata": { "request_id": "5b55d5234719496a956b45feb650dd0b" },
  "error": {
    "code": "rate_limited",
    "type": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "request_id": "5b55d5234719496a956b45feb650dd0b",
    "doc_url": "https://onekey.dev/errors/RATE_LIMITED"
  }
}
```

Wait that long and retry. Do not retry in a tight loop.

Cache-served answers have their own, separate allowance (by default 600 per key and 3,000 per account per minute) because they still cost bandwidth. If you hit it the answer is the same `429` with `Retry-After`.

## Concurrent requests

A key may have **50 requests in flight at once** (a request is in flight from the moment it is accepted until its response, or a stream, has finished). The 51st gets `429 CONCURRENCY_LIMITED` straight away, free, with `Retry-After: 1` and the `X-Concurrency-*` headers:

```bash
# HTTP/1.1 429 Too Many Requests
# retry-after: 1
# x-concurrency-limit: 50
# x-concurrency-remaining: 0
```

It is a separate error from `RATE_LIMITED` because the fix differs: lower your parallelism (a pool of 20 to 40 workers is plenty) rather than your request rate. A [batch](https://onekey.dev/docs/batch.md) is one request however many rows it holds. The cap is counted per server process, so with several processes behind the load balancer the effective ceiling can be a little higher; plan for 50.

## How to retry safely

1. **Honour the wait.** On a `429`, a `503` or an idempotency `409`, read `Retry-After` and sleep that many seconds. Nothing shorter helps.
2. **Back off on server-side failures.** On `500` and `502`, wait 1 s, 2 s, 4 s with a little jitter, up to 3 or 4 tries. Those calls were refunded, so the retry costs nothing extra.
3. **Never retry what will fail again.** `400`, `401`, `402`, `403`, `404`, `405` and `422` fail the same way every time. See [Errors](https://onekey.dev/docs/errors.md).
4. **Make retries idempotent.** Send an `Idempotency-Key` on calls you might have to repeat after a timeout or a lost connection. If the first attempt did succeed, the retry is replayed for free instead of being billed again. See [Idempotency](https://onekey.dev/docs/idempotency.md).
5. **Slow down before the limit.** Watch `X-RateLimit-Remaining` and ease off before it reaches `0` rather than after.
6. **Check the platform first.** When a platform keeps returning `502`, ask the free [status endpoint](https://onekey.dev/docs/status.md): if it says `down`, waiting beats retrying.

## Good habits

- Spread work out instead of firing hundreds of requests at once.
- Cache results on your side when the same data is asked for repeatedly. Onekey's cache already makes repeats cheap, but no request is cheaper than no request.
- Use one key per app, since limits are per key. A [credit limit](https://onekey.dev/docs/key-limits.md) per key also caps what one app can spend.

## Bad-key attempts

Requests with a wrong or missing key are limited separately per client address (`429` after repeated failures), so guessing keys does not work and a misconfigured client backs off quickly. Those responses carry no `X-RateLimit-*` headers because no key was identified.

> [!NOTE] The 600 per minute default may change; the `X-RateLimit-Limit` header always shows the limit that applies to your key. Higher limits for heavy users are available on request: write to us.
