Units and limits
There are two independent limits, and they fail in different ways. Confusing them is the usual reason an integration behaves oddly under load.
| Burst rate limit | Monthly quota | |
|---|---|---|
| Counted per | API key | Account (all your keys together) |
| Unit of account | Requests | Units — endpoints cost different amounts |
| Window | 60 seconds, rolling | Calendar month, UTC |
| Headers | X-RateLimit-* | X-Quota-* |
| When it trips | 429 — wait seconds and retry | 429 — wait until the 1st, or upgrade |
Why units and not requests
A request is not a request. A metadata read is an in-process lookup; a charts call is a multi-query fan-out to a paid upstream that we pay for per call. Charging both the same would mean either overcharging for the cheap one or subsidising the expensive one out of everybody else’s allowance.
| Tier | Units | Endpoints |
|---|---|---|
| Metadata | 0 | /v1/chains, /v1/protocols |
| Detail | 1 | /v1/positions/:chainId/:protocol/:id, /v1/pools/:chainId/:protocol/:id |
| List | 2 | /v1/positions, /v1/pools |
| Charts | 10 | …/charts on either resource |
The live table is served from GET /v1/pricing/units and rendered on the pricing page. Read it from the API rather than hard-coding these numbers.
When the quota resets
00:00 UTC on the 1st of each month. Not a rolling 30 days, and not the anniversary of when you subscribed. X-Quota-Reset is the number of seconds until that instant, so you can act on it without doing calendar arithmetic.
The consequence worth planning for: subscribing on the 20th gives you the whole month’s allowance, and it resets eleven days later. We would rather be clear about that than quietly prorate an allowance nobody can predict.
Requests we refuse are not billed
Units are consumed before your request is served, and credited back on any 4xx. A malformed filter, an unknown position id, a 403 from a server key used in a browser — none of them cost anything. Billing for a request the API declined is not defensible, so we do not.
A 429 is the exception, and only technically: it is rejected before anything is consumed, so there is nothing to credit.
Handling a 429
Both kinds carry Retry-After in seconds. Honour it rather than retrying on a fixed timer — a burst 429 clears in seconds, a quota 429 clears at the month boundary, and the header is how you tell them apart without parsing prose. The body also carries retryAfterSec.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
{"error":"Rate limit exceeded","retryAfterSec":12}If a paid plan lapses
Your account moves to Free and your keys keep working at Free limits. Nothing is revoked and nothing is deleted. Your consumed units for the current month are also reset at that point, so a downgrade cannot leave you locked out for the rest of the month on an allowance you had already spent.