Ir al contenido
KOTOVADOCS
Lanzar App

Rate limits

Ver como Markdown

Esta página aún no está disponible en tu idioma.

Requests are limited per account, not per key, so creating a new key never resets the budget. Every request costs a weight, and the weights of all your account’s requests within a minute must stay inside its limit.

Your account has a budget of weight units per 60-second window. The default is 250 units. Windows follow the clock: each one starts at the full minute (UTC) and resets for everyone at the same moment, rather than 60 seconds after your first request.

Each keyed request is checked first and charged second. If its weight still fits into what is left of the window, it is charged and answered. If it does not fit, it is refused with 429 rate_limited and costs nothing. There is no penalty for being refused: once the window resets, the full budget is available again.

The budget is shared by every request of your account. GET /me reports your limit and today’s usage, and the account API page shows the units used in the current minute and the requests of the last seven days.

Operation Method and path Weight
createOrder POST /orders 50
createQuote POST /quote 1
getOrder GET /orders/{id} 1
listOrders GET /orders 1
validateAddress POST /addresses/validate 1
getMe GET /me 1
Keyless endpoints /health, /openapi.json, /currencies, /networks, /sources, /pairs, the rates feed and the index none

The OpenAPI document gives each keyed operation’s weight as x-kotova-weight. At the default limit of 250 units, an account can create at most five orders a minute, and fewer when it also quotes and reads in the same minute.

A keyed request costs its weight whatever it answers, errors included: a quote answered with 422 amount_below_min, for example, still costs one unit. The exceptions:

  • Refused by the limit. A request refused with 429 rate_limited costs nothing. Its X-Kotova-Weight header reports what it would have cost.
  • Refused before the limit is checked. These cost nothing and carry no rate-limit headers: every authentication failure (401 unauthorized, 401 key_revoked, 403 affiliate_inactive, 429 rate_limited with details.reason: "auth_failures"), and an unknown or repeated query parameter (400 invalid_request).
  • A quote refused by the fan-out allowance. 429 rate_limited with details.reason: "fanout" costs nothing (see Quotes that ask the sources).
  • Creating an order that fails before a source is contacted. POST /orders costs 1 instead of 50 when it answers an error before the API has asked any source to create the order: an invalid body, a quote that is unknown, used or expired, an address that fails the format check, an order cap, or sources that are all unavailable, for example. Once a source has been asked, the full 50 stays charged, once, whatever the outcome and however many of the quote’s sources were tried.
  • A replay of an order. A POST /orders that repeats an earlier request with the same Idempotency-Key and body answers that order (Idempotent-Replayed: true) and costs 1.

Units given back go into the window the request was charged in.

Keyed responses carry these headers once the key is authenticated. Your code should still work when they are missing.

Header Example Meaning
X-Kotova-Weight 1 Units this request was charged. On a refused request, what it would have cost.
RateLimit-Policy "units";q=250;w=60 Your budget: q units per window of w seconds.
RateLimit "units";r=187;t=34 r units left in this window, t seconds until it resets.
X-RateLimit-Limit 250 Units per 60-second window.
X-RateLimit-Remaining 187 Units left in this window.
X-RateLimit-Reset 34 Seconds until the window resets.
Retry-After 34 Seconds to wait before retrying. Sent with every 429, and with some 409 and 503 answers.

A complete set, as it arrives with a quote:

HTTP/2 200
X-Request-Id: req_8fK2mQ9xLp3vTz7nWc1bYd4h
X-Kotova-Weight: 1
RateLimit-Policy: "units";q=250;w=60
RateLimit: "units";r=187;t=34
X-RateLimit-Limit: 250
X-RateLimit-Remaining: 187
X-RateLimit-Reset: 34
X-Kotova-Quote-Cache: miss

Keyless responses carry no rate-limit headers, because they cost no units.

A request that does not fit is answered 429 with Retry-After:

{
"error": {
"code": "rate_limited",
"message": "Rate limit of 250 units per 60 s reached. Retry in 34 s.",
"request_id": "req_8fK2mQ9xLp3vTz7nWc1bYd4h",
"details": { "limit": 250, "remaining": 0, "reset_seconds": 34, "weight": 1 },
"doc_url": "https://docs.kotova.io/x/api/errors#rate_limited"
}
}

Wait for the number of seconds in Retry-After, then send the request again. Better, watch X-RateLimit-Remaining and slow down before you reach zero, keeping room for the 50 units an order costs.

Terminal window
# -D - prints the response headers, including the rate-limit ones.
curl -sS -D - -o /dev/null https://api.kotova.io/x/v1/me \
-H "Authorization: Bearer $KOTOVA_API_KEY"

A retry of POST /orders must carry the same Idempotency-Key and the same body as the first attempt, so that it can never create a second order. See Errors.

Quotes are first looked up in a shared cache that keeps the sources’ answers for up to 30 seconds for a fixed rate and 60 seconds for a variable one. A quote the cache cannot answer asks the sources again, and the response says so in X-Kotova-Quote-Cache: miss (hit when the cache answered). See Caching and the fan-out allowance.

Besides their weight, quotes that ask the sources count against a separate allowance per account and minute: your limit divided by 8, rounded down, and at least 30. Over it, POST /quote answers 429 rate_limited with details.reason: "fanout" and Retry-After, and the refused quote costs nothing. Many quotes for slightly different amounts each ask the sources; reusing amounts lets the cache answer more of them.

Two caps limit how many orders an account can have, whatever its budget:

  • Open orders waiting for a deposit: 20 × your limit ÷ 250. These are orders still in waiting_for_deposit, created with any of your account’s keys.
  • Orders created in the last 24 hours: 100 × your limit ÷ 250.

Both are rounded down, and each is at least 1. Over either cap, POST /orders answers 429 order_cap_reached, with details.cap (open_unfunded or created_24h), details.limit, details.current and Retry-After. An open order stops counting once it is funded or expires.

Rate limit (units per minute) Quotes that ask the sources, per minute Open orders waiting for a deposit Orders per 24 hours
250 (default) 31 20 100
500 62 40 200
1000 125 80 400
2500 312 200 1000
5000 625 400 2000

Keyless endpoints cost no units. Most of their answers come from the CDN cache, and those count against nothing. Calls that reach the server are limited to 120 per minute per IP address (per /64 for IPv6); the XML rates feed has its own allowance of the same size, and BestChange’s monitoring robots are exempt from it. Over the limit, the answer is 429 rate_limited with details.reason: "ip" and Retry-After.

The surest way to stay well inside it is to send no key, no varying query strings and no cache-busting headers, so that the CDN can answer, and to cache the catalogue on your side: it changes rarely, and its responses carry an ETag you can send back as If-None-Match to get a 304.

Sources have limits of their own, which show up in these answers:

  • A source that cannot be asked right now is left out of a quote, and the quote lists it in a sources_temporarily_skipped warning. When no source gave a usable quote and at least one could not be asked right now, POST /quote answers 503 sources_unavailable with Retry-After (10 seconds).
  • A source that cannot take more orders through the API for the moment is left out of quotes. If it was the only one, the quote fails with 422 no_source_available and the reason create_budget_exhausted.
  • When the source of your quote cannot take the order, POST /orders places it at the next of the quote’s fallback sources that can (see Next best source). When none of them is available, or with retry_next_best_source: false, it answers 503 source_unavailable. Request a new quote; it may come from other sources.

Your current limit is on the account API page. To raise it, choose Request more next to the rate limit, or Request higher limits under Access requests, and pick one of the offered tiers: 500, 1000, 2500 or 5000 units per minute. Tell us what you are building: your product, your users, the pairs you need and your expected monthly volume. Every request is reviewed by hand, and the decision appears on the same page. You can have one open request per type.

A higher limit belongs to your account, like the default one, so it stays when you regenerate your key, and the fan-out allowance and the order caps grow with it. For more than the highest tier, write to api@kotova.io.