Errors
The error envelope, every status code the API returns, and what to do about each one.
The envelope
Every error — at every status code — has the same shape:
Branch on error.code, not on error.message. The codes are stable; the
messages are written for humans and may be reworded at any time.
Status codes
| HTTP | error.code | When | What to do |
|---|---|---|---|
| 400 | INVALID_INPUT | Bad query parameter — unknown key, or limit outside 1–100 | Fix the request. Retrying unchanged won't help. |
| 401 | UNAUTHORIZED | Missing, malformed, or unknown token | Check the Authorization header; regenerate the token if needed. |
| 402 | PAYMENT_REQUIRED | Token is valid but the subscription has lapsed | Renew the subscription; the same token starts working again. |
| 403 | FORBIDDEN | API access is not available on this account, or an OAuth token is missing a scope | Check the account's subscription state, or request the missing scope. Retrying won't help. |
| 404 | NOT_FOUND | No such route, or the board doesn't exist / isn't accessible to the caller | Check the path and the board id. |
| 429 | TOO_MANY_REQUESTS | Rate limit exceeded | Wait Retry-After seconds, then retry. |
| 500 | INTERNAL_ERROR | Something broke on our side | Retry with backoff. If it persists, tell us. |
Insufficient scope
Only OAuth tokens can hit this — a personal access token
represents you and carries every scope. When a call falls outside what the user
approved, the response is 403 with a WWW-Authenticate header naming exactly
what was missing:
It's 403 rather than 401 on purpose (RFC 6750 §3.1):
the token is valid, it just doesn't cover this call. Re-authenticating with the
same scopes won't fix it — send the user through authorization again asking for
the additional scope. Read the scope parameter from the header rather than
guessing, and note it can name more than one scope, space-separated.
Unknown query parameters are rejected
Paginated endpoints reject query keys they don't recognise rather than ignoring them:
This is deliberate. A silently ignored limt=10 would page through your entire
library at the default page size while you believed you'd asked for ten.
404 on boards is intentionally ambiguous
/v1/boards/{boardID}/saves returns 404 both when a board doesn't exist and
when it exists but you have no role on it. The API won't confirm the existence
of someone else's board, so you can't tell those cases apart — and neither can
anyone probing for board ids.
Retrying
429and500are worth retrying. HonourRetry-Afteron a429; use exponential backoff on a500.400,401,403and404will return the same result no matter how many times you retry. Fix the request or the account state.402resolves itself when billing does — retry on a slow schedule (hourly, not per-second) if you want a lapsed integration to recover on its own.