API
Errors
One shape, eight codes, and what to do about each.
The shape
Every failure — including a 404 on a path that is not an endpoint — comes back as an error object. There are no exceptions, so one handler covers the whole API.
{
"error": {
"code": "rate_limited",
"message": "Rate limit of 10 requests per minute exceeded. Retry in 35s.",
"retryAfter": 35
}
}Branch on code, never on message. The codes are part of the contract and will not change under you; the messages are written for people and get reworded whenever we can say something more clearly. details appears on validation errors (an array of offending fields) and on rate_limited (as retryAfter).
Note this differs from the app’s own routes under /api, which answer { "error": "a sentence" }. Those are the frontend’s private contract and are not the API.
The codes
invalid_requestWhen. A query or body parameter is wrong. details names each offending field.
Do. Fix the request. Retrying it unchanged will fail identically.
unauthorizedWhen. No key, a malformed Authorization header, or a key that no longer exists.
Do. Stop. Check the key was not deleted, and that the header reads Authorization: Bearer <your key>.
email_not_verifiedWhen. The account has not confirmed its email address.
Do. A person must verify it in the app. No amount of retrying helps.
plan_requiredWhen. The account is on Free. The key is valid; the plan does not include API access.
Do. Upgrade. The same key starts working immediately, with nothing to recreate.
not_foundWhen. No such board, one you do not track, or a path that is not an endpoint.
Do. Do not retry. For boards, the two cases are deliberately indistinguishable.
already_trackedWhen. POST /v1/boards for a board already on your list. details.boardId is the existing one.
Do. Treat as success if you were reconciling a list; use the id it gives you.
rate_limitedWhen. Over ten requests in the last minute, across all of the account's keys.
Do. Wait retryAfter seconds — or the Retry-After header, which says the same thing — then retry.
internal_errorWhen. Our fault.
Do. Safe to retry with backoff. If it persists, tell us.
Retrying
Only two of these are worth retrying automatically: rate_limited, after the interval it names, and internal_error, with backoff. Everything else is a request that will fail the same way forever, or a state a person has to change.
Retrying a 429 immediately makes things worse: the window is a sliding one, so a retry inside it both fails and counts. Honour Retry-After.