Rate limits and quotas
How the Decryptr API counts calls (one per item, weighted by endpoint), the limits per plan, the headers on every response, 429 and Retry-After, and a backoff example.
Everything is counted in calls, and there are three limits on them:
- Calls per month. What you pay for. A hard cap with no overage charges.
- Calls per minute. A fixed window per clock minute.
- Burst. A bucket that holds your plan's maximum calls per request and refills at the per-second rate, so one full request always fits in a full bucket.
Calls: one per item, weighted
A call is counted by what you get back or ask for, not by HTTP request. Most items cost 1 call. Richer items cost more.
- A list costs its weight for each item it returns. A page of 37 trending coins costs 37 calls. A page of 20 footprint candles costs 40, because a footprint candle weighs 2.
- A batch costs its weight for each id you ask for.
/v1/sentiment/callers?assets=BTC,ETH,SOL,DOGE,XRPcosts 5 calls, even if one of the assets has no data. - A single lookup has a flat cost: 1 call for
/v1/analytics/crowd/BTC, 2 for/v1/coins/BTC, 3 for/v1/liquidations/BTC. A lookup that finds nothing still pays. - A search costs 5 calls plus 1 call per result. A search with no results costs 5.
- A stream connection is free. Each event delivered costs 1 call, taken before it is sent.
- An empty list costs 1 item's worth, so polling an empty list is not free.
/v1/status,/v1/usageand/v1/tombstonesare free.- Test keys never count towards the month.
Every response tells you what it cost in the X-Decryptr-Cost header. The API reference shows the cost on each endpoint.
| Endpoint | Cost in calls | Reserved before it runs |
|---|---|---|
GET /v1/status | Free | Nothing |
GET /v1/usage | Free | Nothing |
GET /v1/trending/coins | 1 call per item returned | limit |
GET /v1/calls/outcomes | 1 call per item returned | limit |
GET /v1/calls/most-called | 1 call per item returned | limit |
GET /v1/sentiment/callers | 1 call per id asked for | Number of ids |
GET /v1/sentiment/{asset} | 1 call per item returned | limit |
GET /v1/analytics/crowd/{asset} | 1 call, also when not found | 1 |
GET /v1/coins/{asset} | 2 calls, also when not found | 2 |
GET /v1/prop/programs | 1 call per item returned | limit |
GET /v1/levels/rounds | 1 call per item returned | limit |
GET /v1/levels/rounds/{id} | 1 call, also when not found | 1 |
GET /v1/liquidations/{asset} | 3 calls, also when not found | 3 |
GET /v1/orderflow/delta | 1 call per item returned | limit |
GET /v1/orderflow/footprint | 2 calls per item returned | limit x 2 |
GET /v1/derivatives/{asset}/funding | 1 call per item returned | limit |
GET /v1/whales/positions | 2 calls per item returned | limit x 2 |
GET /v1/whales/positioning | 2 calls per item returned | limit x 2 |
GET /v1/calls | 1 call per item returned | limit |
GET /v1/calls/{id} | 1 call, also when not found | 1 |
GET /v1/callers | 3 calls per item returned | limit x 3 |
GET /v1/callers/{handle} | 3 calls, also when not found | 3 |
GET /v1/traders | 3 calls per item returned | limit x 3 |
GET /v1/traders/batch | 3 calls per id asked for | Number of ids |
GET /v1/traders/{id} | 3 calls, also when not found | 3 |
GET /v1/leaderboard | 1 call per item returned | limit |
GET /v1/tombstones | Free | Nothing |
GET /v1/signals | 1 call per item returned | limit |
GET /v1/signals/{id} | 1 call, also when not found | 1 |
GET /v1/signals/{id}/impact | 2 calls, also when not found | 2 |
GET /v1/sentiment/{asset}/news | 1 call per item returned | limit |
GET /v1/stream/signals | 1 call per event delivered, connecting is free | Nothing at connect |
GET /v1/trending/topics | 1 call per item returned | limit |
GET /v1/calendar | 1 call per item returned | limit |
GET /v1/events/{id}/calls | 1 call, also when not found | 1 |
GET /v1/profiles/{handle} | 1 call, also when not found | 1 |
GET /v1/search/people | 5 calls, plus 1 call per item returned | 5 + limit |
GET /v1/posts | 1 call per item returned | limit |
GET /v1/posts/{id} | 1 call, also when not found | 1 |
GET /v1/posts/{id}/replies | 1 call per item returned | limit |
GET /v1/coins/{asset}/posts | 1 call per item returned | limit |
GET /v1/trending/posts | 1 call per item returned | limit |
GET /v1/search/posts | 5 calls, plus 1 call per item returned | 5 + limit |
GET /v1/stream/posts | 1 call per event delivered, connecting is free | Nothing at connect |
GET /v1/communities | 1 call per item returned | limit |
GET /v1/communities/{slug} | 1 call, also when not found | 1 |
GET /v1/communities/{slug}/posts | 1 call per item returned | limit |
GET /v1/lists/{id} | 1 call per item returned | limit |
GET /v1/leaderboards/levels | 1 call per item returned | limit |
GET /v1/traders/{id}/prop | 3 calls, also when not found | 3 |
GET /v1/traders/wallets | 3 calls per item returned | limit x 3 |
GET /v1/communities/{slug}/activity | 1 call per item returned | limit |
GET /v1/communities/{slug}/members/history | 1 call per item returned | limit |
GET /v1/rooms | 1 call per item returned | limit |
GET /v1/rooms/{id}/activity | 1 call per item returned | limit |
GET /v1/tags/{tag}/volume | 1 call per item returned | limit |
GET /v1/trending/tags | 1 call per item returned | limit |
GET /v1/levels/rounds/{id}/distribution | 2 calls, also when not found | 2 |
GET /v1/prop/firms/{firm}/stats | 1 call, also when not found | 1 |
GET /v1/whales/leaderboard | 1 call per item returned | limit |
GET /v1/market/coins | 1 call per item returned | limit |
GET /v1/market/coins/{asset} | 1 call, also when not found | 1 |
GET /v1/market/tickers | 1 call per id asked for | Number of ids |
GET /v1/market/candles | 1 call per item returned | limit |
GET /v1/market/funding | 1 call per item returned | limit |
GET /v1/market/open-interest | 1 call per item returned | limit |
GET /v1/market/long-short | 1 call per item returned | limit |
GET /v1/market/long-short/exchanges | 1 call per item returned | limit |
GET /v1/market/liquidations/heatmap | 2 calls per item returned | limit x 2 |
GET /v1/market/liquidations/totals | 1 call per item returned | limit |
GET /v1/market/trending | 1 call per item returned | limit |
Reserved before it runs
Before a list runs, the API reserves the most it could cost: limit times the calls per item. If your remaining budget cannot cover that, the request is refused with 429 and nothing is read or charged. Ask for a smaller limit to fit. After the request runs, the unused part comes back and you pay only for what was returned.
A worked example
You are on Starter and have 60 calls left this month.
| Request | Reserved | Result | Cost | Left |
|---|---|---|---|---|
/v1/liquidations/BTC | 3 | Runs | 3 | 57 |
/v1/orderflow/delta?asset=BTC&limit=25 | 25 | Fits, returns 25 | 25 | 32 |
/v1/orderflow/delta?asset=BTC&limit=25 | 25 | Fits, returns 25 | 25 | 7 |
/v1/orderflow/delta?asset=BTC&limit=25 | 25 | 25 does not fit in 7, refused with 429 quota_exceeded | 0 | 7 |
/v1/sentiment/callers?assets=BTC,ETH,SOL,DOGE,XRP | 5 | Runs, DOGE has no data | 5 | 2 |
/v1/coins/BTC | 2 | Runs | 2 | 0 |
/v1/usage | 0 | Runs, free | 0 | 0 |
Page sizes
Each plan sets the most calls one request may reserve. For searches the 5 call base counts too. The most items per request is that number divided by the endpoint's weight, rounded down. On Pro (50), you can ask for 50 trending coins, or 25 footprint candles. Asking for more is refused with 400 limit_too_large, and the error's max_items says what your plan allows.
When calls run out
Every plan is a hard cap. There are no surprise bills: requests are refused with 429 quota_exceeded until the month resets on the 1st (UTC) or you upgrade. See Plans and pricing.
Limits per plan
The per-second figure is how fast the burst bucket refills. All figures are calls.
| Plan | Calls / second (refill) | Calls / minute | Calls / month | Max calls per request |
|---|---|---|---|---|
| Developer | 2 | 20 | 1,000 | 10 |
| Starter | 5 | 60 | 50,000 | 25 |
| Pro | 15 | 300 | 300,000 | 50 |
| Business | 40 | 900 | 1,500,000 | 100 |
| Enterprise | By contract | By contract | By contract | By contract |
Headers on every response
Every authenticated response, including a 429, carries these:
X-Request-Id: 3f6c2a8e-6a1f-4c1e-9d2b-0a5e7c1d9b42
X-Decryptr-Cost: 37
RateLimit-Policy: "burst";q=25;w=1, "minute";q=60;w=60, "month";q=50000;w=2592000
RateLimit: "minute";r=23;t=37
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 23
X-RateLimit-Reset: 37
X-RateLimit-Burst-Remaining: 12
X-Quota-Limit: 50000
X-Quota-Remaining: 48210| Header | Meaning |
|---|---|
X-Decryptr-Cost | Calls this request was charged. |
X-RateLimit-Limit | Calls allowed per minute. |
X-RateLimit-Remaining | Calls left in this minute, after this request. |
X-RateLimit-Reset | Seconds until the minute window resets. |
X-RateLimit-Burst-Remaining | Calls left in the burst bucket. |
X-Quota-Limit | Calls allowed per calendar month (UTC). |
X-Quota-Remaining | Calls left this month, after this request. |
RateLimit-Policy, RateLimit | The same limits in the IETF draft 11 format. |
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset | Older draft names for the minute window. |
Retry-After | On a 429: seconds to wait before trying again. |
/v1/usage returns your plan, limits and calls used as JSON, and is free to call.
Handling 429
A 429 always comes with Retry-After and a problem body whose code says which limit you hit. For rate_limited and quota_exceeded, the body also has limit (burst, minute or quota), calls_requested and retry_after.
| Code | What to do |
|---|---|
rate_limited | Wait Retry-After seconds, then retry. If the request alone is bigger than your bucket, ask for a smaller limit. |
quota_exceeded | Not enough calls left this month. Ask for a smaller limit, wait for the reset, or upgrade. Retrying the same request will fail again. |
too_many_auth_failures | Too many failed key checks from your client. Fix the key, then wait Retry-After seconds. |
stream_limit | You already have your plan's number of open streams (2 on Pro, 10 on Business, shared by the signal and post streams). Close one, or reconnect after the server ends a connection. |
Retry rate_limited with exponential backoff and jitter, and respect Retry-After when it is longer:
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function getWithBackoff(url, key, maxTries = 5) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
if (res.status !== 429) return res;
const problem = await res.json();
// Only the request rate is worth retrying; the other 429s will fail again.
if (problem.code !== "rate_limited" || attempt + 1 >= maxTries) return res;
const retryAfter = Number(res.headers.get("Retry-After") ?? 1) * 1000;
const backoff = Math.min(30_000, 500 * 2 ** attempt);
await sleep(Math.max(retryAfter, backoff) + Math.random() * 250);
}
}