Error shape and rate limits
Failures are standard NestJS error bodies — a status code plus a message. Throttling is per endpoint class, not one global bucket.
The body
{ "statusCode": 404, "message": "Token not found: 0x…" }Some read endpoints return an error object with a 200 instead — the quote and build-tx validators reply {"error": "…"} rather than raising, so check the body even on a 200. Validation errors that reject a request body return a 400 with a message or an array of messages.
Status codes
| Code | When you get it |
|---|---|
| 400 | Body fails validation — bad order params, malformed address, oversized upload fields |
| 401 | A guarded route hit without credentials — missing X-API-Key, or an expired session cookie |
| 403 | Authenticated but refused: read-only key on sign-and-submit, key used from a non-allowlisted IP, polling someone else's agent job |
| 404 | Unknown token, quote asset, stock symbol, or route — 404s are for things that don't exist, not things you're not allowed to see |
| 413 | Image upload over 5 MB |
| 429 | Throttled — see the limits below |
- 400
- When you get it
- Body fails validation — bad order params, malformed address, oversized upload fields
- 401
- When you get it
- A guarded route hit without credentials — missing X-API-Key, or an expired session cookie
- 403
- When you get it
- Authenticated but refused: read-only key on sign-and-submit, key used from a non-allowlisted IP, polling someone else's agent job
- 404
- When you get it
- Unknown token, quote asset, stock symbol, or route — 404s are for things that don't exist, not things you're not allowed to see
- 413
- When you get it
- Image upload over 5 MB
- 429
- When you get it
- Throttled — see the limits below
Rate limits
| Class | Limit |
|---|---|
| Global default | From GET /config — platform.rateLimit (requests / window seconds) |
| POST /wallet/sign-and-submit | 30 / minute — the tightest limit in the API |
| GET /auth/login | 10 / 15 min |
| POST /wallet/export-key | 3 / hour — exporting a private key is throttled hardest |
| Logo reads, public profiles | Exempt — they're hot paths for every page render |
- Global default
- Limit
- From GET /config — platform.rateLimit (requests / window seconds)
- POST /wallet/sign-and-submit
- Limit
- 30 / minute — the tightest limit in the API
- GET /auth/login
- Limit
- 10 / 15 min
- POST /wallet/export-key
- Limit
- 3 / hour — exporting a private key is throttled hardest
- Logo reads, public profiles
- Limit
- Exempt — they're hot paths for every page render
The global number is config-driven and can change — read GET /config rather than hardcoding a guess. On a 429, back off and retry; there is no Retry-After contract yet.
429 is not a bug to retry around blindly
If a loop is hitting the throttle, the answer is almost always a stream — /tokens/stream pushes every price update over SSE and removes the polling entirely.