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. |
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, which is public, max-age=30. |
A fresh fetch looks like this (dates and other standard headers left out):
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-storeThe 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.
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{
"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:
# HTTP/1.1 429 Too Many Requests
# retry-after: 1
# x-concurrency-limit: 50
# x-concurrency-remaining: 0It 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 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#
- Honour the wait. On a
429, a503or an idempotency409, readRetry-Afterand sleep that many seconds. Nothing shorter helps. - Back off on server-side failures. On
500and502, 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. - Never retry what will fail again.
400,401,402,403,404,405and422fail the same way every time. See Errors. - Make retries idempotent. Send an
Idempotency-Keyon 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. - Slow down before the limit. Watch
X-RateLimit-Remainingand ease off before it reaches0rather than after. - Check the platform first. When a platform keeps returning
502, ask the free status endpoint: if it saysdown, 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 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.