A budget, weighted by what you ask for
A quote is a read against in-memory pool state. A swap runs that same routing and then builds a transaction, resolves its lookup tables, and simulates it. Charging both “one request” makes the budget meaningless for whichever of the two you sized it against, so each request debits your bucket by its weight:Limits by plan
Through the cost table, a budget of N units/second is N quotes/second, or N/5 transaction
builds/second, or any mix of the two — Builder’s 120 units is 120 quotes/second or 24 builds.
Limits are per account, not per key. Creating more keys splits one integration’s traffic
across them; it does not add budget. The same holds for stream connections: they are counted
across every key on the account.
Stream connections
GET /api/v1/stream is available from Starter up, capped at the plan’s connection count across
the whole account. An upgrade on a plan without the stream returns 403 with code
STREAM_NOT_IN_PLAN; one past the limit returns 429 with code STREAM_LIMIT. A closed
connection frees its slot immediately.
Free-plan keys keep one stream connection until 14 November 2026, so integrations built
while the stream was free have time to move to a paid plan.
Anonymous access (no API key)
GET /api/v1/quote, POST /api/v1/swap, POST /api/v1/instructions, and GET /api/v1/price
all work with no Authorization header — useful for trying the API before you get a key.
Anonymous callers share a small per-IP bucket, debited by the same cost table:
A burst of 20 covers one
POST /api/v1/swap (5 units) plus a handful of quotes. Anonymous access is for evaluation; for anything
else get a key — same mechanics, at least 10× the budget, scoped to your
account rather than shared with every anonymous caller on your IP.
GET /health requires no key and is not subject to any of these limits.
Per-IP ceiling (before authentication)
A second limiter runs before authentication and buckets by client IP rather than by key. It protects the auth path from unauthenticated floods, so it applies to every/api/v1/* request —
including ones with a missing or invalid key — and to the WebSocket upgrade.
It sits at twice the highest plan’s budget so it never binds on legitimate traffic from a
single customer; it should only trip on abuse. If you proxy many customers through one egress IP,
they share this ceiling even though their per-account budgets are separate.
The WebSocket handshake is covered by this per-IP limiter and by the plan’s connection limit —
the per-account request budget is not applied to the upgrade. Once connected, the stream is not
rate limited per message.
Rate limit response
When rate limited, the API returns HTTP429:
Rate limit headers
Every rate-limited response, successful or not, reports where your bucket stands. Units are cost units, not requests: a transaction build spends 5, a quote 1.
The headers are exposed over CORS, so browser code can read them. Anonymous requests report
the anonymous bucket. The pre-auth per-IP ceiling sends only
Retry-After.
Retry strategy
On a429, wait Retry-After seconds if it is present. It is computed from your plan’s
refill rate and the cost of the request you just made. Otherwise, use exponential backoff:
Best practices
- Don’t cache quotes. A quote’s firm commitment lives for
validForMs(~3 s) and its price-or-fail window forfirmForMs(~400 ms) — see Firm quotes. Re-fetch before you build. (lastValidBlockHeightbounds how long the transaction can land, ~60 s; that is a different clock from how long the price is honoured.) - Stream instead of polling. If your plan includes the stream and you need continuously fresh
prices, subscribe on
GET /api/v1/stream— the connection is not per-message rate limited, so it is strictly cheaper than spending 1 unit per/quote. - Prefer
/instructionsover/quote+/swap. OnePOST /api/v1/instructionscosts 5 units; aGET /api/v1/quotefollowed byPOST /api/v1/swapcosts 6 and makes the engine route twice.