Rate limits and quotas
Two separate mechanisms limit use of the API: rate limits cap how many requests you can send per minute, and quota and balance pay for the checks you run. They fail differently, so handle them differently.
Rate limits
Limits are counted per minute, and separately for each combination of caller, HTTP method and route: polling GET /v1/checks/{check_id} does not use up your POST /v1/checks allowance. Every request passes two ceilings, and the first one reached answers 429.
| Ceiling | Limit per minute |
|---|---|
| Per source IP address (IPv6 clients are counted per /64 network) | 30 |
| Per live API key | 60 |
| Per sandbox API key | 10 |
| Failed authentication, per source IP address | 5 |
| Rederive, per account across all its keys | 20 |
| Address reports, per account across all its keys | 10 |
The ceilings apply to each route on its own. For example, a single server sending every live request from one IP address reaches the 30-per-minute IP ceiling before the 60-per-minute key ceiling. To screen more than that in a minute, spread the traffic across addresses or send up to 100 checks in one POST /v1/checks/batch request, which counts as one request. If your volume needs higher limits, write to hello@leeri.ai.
A throttled request answers 429 with RATE_LIMIT_EXCEEDED and these headers:
| Header | Meaning |
|---|---|
Retry-After | Seconds until the window resets. |
RateLimit-Limit | Requests permitted per window for this caller, method and route. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
Wait for Retry-After seconds and send the same request again. Too many failed authentications from one address also answer 429 for the rest of the minute, so a bad key in a retry loop locks that address out: stop on 401.
- curl
- Node.js
- Python
curl -s -i -X POST https://api.leeri.ai/v1/checks \
-H "Authorization: Bearer $LEERI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subject":"address","asset":"TRX","address":"'"$ADDRESS"'"}' | grep -i -E '^(HTTP|retry-after|ratelimit)'
async function postWithRetry(url, options) {
for (let attempt = 1; attempt <= 4; attempt += 1) {
const response = await fetch(url, options);
if (response.status !== 429) return response;
const wait = Number(response.headers.get('retry-after') ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
throw new Error('still rate limited after 4 attempts');
}
import time
import requests
def post_with_retry(url, **kwargs):
for _ in range(4):
response = requests.post(url, timeout=30, **kwargs)
if response.status_code != 429:
return response
time.sleep(int(response.headers.get("Retry-After", "1")))
raise RuntimeError("still rate limited after 4 attempts")
How a check is paid for
Each billing period your plan includes a number of checks, its quota. A check is paid in this order:
- From the remaining quota.
- When the quota is used up, from the prepaid balance, at the per-check price of your plan.
- When both are exhausted, the request is refused with
402INSUFFICIENT_BALANCEand no check is run.
A batch is admitted all or nothing: if the quota and balance cannot cover every item, the whole batch is refused. Plans and prices are on the pricing page.
What is not charged
- A re-check within 7 days. If you were charged for the same address on the same chain in the last 7 days, checking it again is free, whether as an address check or a transaction check. This lets you re-screen a customer without paying twice.
- Sandbox checks. A sandbox key never uses quota or balance. See Sandbox and live.
- Refused and failed requests. An unsupported asset, a validation error, a failed check and
RULESET_UNAVAILABLEare never charged. - A partial verdict caused by an unavailable data source. If a source the verdict depends on was unavailable or stale, the verdict says so in
partial_reasonsand the charge is returned: retrying later can complete it.
What is charged
A completed verdict is charged. So is a partial verdict whose limit is a stated bound of the analysis rather than an outage, such as a traversal limit or the direct-counterparties-only depth on Ethereum, BNB Smart Chain and Base. That verdict is the full service for that address and re-running it changes nothing.
The evidence report is part of the check and is never billed separately. The charge is settled when the check finishes, so a check that is pending is not final until it completes.
Capacity
Live checks that need a third-party data source hold capacity until their lookups are done. If your account's capacity is full, the request answers 429 PROVIDER_CAPACITY_EXCEEDED with Retry-After: 30; nothing is accepted or charged, so retry after the interval. The default capacity admits 100 live checks waiting on data sources at once, which always fits one full batch of 100.
A single request that needs more capacity than the account holds in total answers 422 PROVIDER_CAPACITY_REQUEST_TOO_LARGE. It can never succeed and carries no Retry-After: send fewer items per request.
See Errors for every code and Sandbox and live for the lower sandbox ceiling.