Rate limits and errors
What each route costs against its limit, what a 429 looks like, and every error the API returns.
Rate limits
Every route has a class, and each class has its own bucket per client address. The class of every operation is in the
reference as x-rate-limit-class. A signed-in session draws from a larger bucket than an anonymous address, and there is
a ceiling above both.
These are the proposed numbers per anonymous address, per minute, with the burst each bucket allows:
| Class | Requests per minute | Burst | Used by |
|---|---|---|---|
cheapRead | 600 | 120 | Cached or single-row reads: launches, one launch, trades, chart, stats, transaction status |
expensiveRead | 60 | 20 | Chain scans, searches and aggregates: holders, search, wallet routes |
quote | 120 | 30 | POST /v1/trades/quote |
build | 30 | 10 | POST /v1/trades/build, POST /v1/transactions/submit |
create | 10 per hour | 5 | POST /v1/launches/build-create |
preview | 60 | 20 | POST /v1/launches/preview |
GET /healthz is not limited. The realtime stream has its own caps (realtime).
Dev observes, it does not refuse yet
On dev the limiter is in observe mode: requests are classified and counted, and none is refused. Treat the numbers above as the contract to write to, because enforcement is switched on after the counts are read. They are proposed values and may change before that.
A 429
{ "error": "Too many requests. Try again in 3 seconds.", "code": "rateLimited", "class": "quote", "retryAfter": 3 }The response carries a Retry-After header and Cache-Control: no-store. Wait that long, then retry. A refused request
never reaches its handler. If the shared counter is briefly unavailable, creation routes answer 503 with
code: "limitsUnavailable" and a retryAfter; reads, quotes and builds keep working on a per-server bucket.
Behind a proxy, the limiter counts the client address the edge reports, so two people behind two proxies are two buckets. A forged forwarding header does not buy a new bucket.
Errors
Every refusal is a JSON object with a sentence in error. Some carry a code and fields you can act on.
| Status | Means | Do |
|---|---|---|
| 400 | Malformed. Usually a number where a base-unit string was required, or a repeated query parameter (code=badParam). | Fix the request. |
| 403 | Where the relay is restricted (on mainnet, which is not live yet), it sends only transactions this API built. | Build the trade again. |
| 404 | No such launch, quote, mint or signature. | Look it up again. |
| 409 | The launch is not in a state that accepts this. | Read status. retryWhenMigrated: true means wait. opensAt is when a market opens. |
| 410 | The quote expired. | Quote again and build at once. |
| 422 | Well formed, and cannot be satisfied. The message names the binding limit. | Read it and adjust. retryWithNewQuote: true means quote again. |
| 429 | Rate limited. | Wait retryAfter seconds. |
| 503 | A chain read failed upstream, or the relay is unavailable. | Retry once, with a pause. |
A 422 deserves real handling and not a generic "transaction failed". It carries the numbers: an amount below the
minimum names that minimum and the decimals; a market that has not opened gives opensAt; a trade that cannot be
expressed in one transaction is refused before a signature is requested, never after.
Error bodies never carry SQL, a stack, a hostname or a secret.