Docs
View as Markdown

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#

HeaderWhenMeaning
X-Request-IdEvery responseCorrelation 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-CacheEvery 200 from a data endpointHIT: served from Onekey's cache (fresh or stale) or replayed for an Idempotency-Key. MISS: fetched from the source for this call.
X-Credits-RemainingA 200 that charged somethingYour 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-LimitEvery response to a request with a valid key, 429 includedRequests per minute this key may make.
X-RateLimit-RemainingSameRequests left in the current minute, never below 0.
X-RateLimit-ResetSameSeconds until the allowance is fully restored.
X-Concurrency-LimitEvery response to a request with a valid key, 429 includedHow many requests this key may have in flight at once (default 50). Omitted when no cap applies.
X-Concurrency-RemainingSameSlots left, counting the request you just made as in flight.
Retry-After429, 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-ReplayIdempotent replays onlytrue. The body is the one stored for your Idempotency-Key; see Idempotency.
Cache-ControlEvery /v1/ responseprivate, 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):

shell
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.

shell
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:

shell
# 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 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.
  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.
  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: 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 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.