Errors
Every error from the public API uses the same JSON shape, so you can handle failures in one place. Branch on code in your code, and show message to people only as a hint, since its wording can change.
Error shape
{
"error": {
"code": "forbidden",
"message": "Your role cannot move cards"
}
}
The code is stable. The message is human-readable and may change between releases.
Status codes and error codes
| Status | Code | Meaning |
|---|---|---|
400 | invalid_body | The request body isn't valid JSON. |
401 | missing_key | No API key was sent. Send Authorization: Bearer <key> or the x-api-key header. |
401 | invalid_key | The key is invalid, expired, or revoked, or its owner is banned or no longer active. |
403 | insufficient_scope | The key doesn't have the Read, Create, or Move permission this endpoint needs. |
403 | organization_not_allowed | The key isn't granted access to the organization that the request targets. |
403 | forbidden | The key owner isn't a member of the organization, or their role doesn't allow this action. |
404 | not_found | The board, list, card, or move destination doesn't exist or isn't visible to the key owner. A move destination on another board also returns 404. |
409 | conflict | A move's expectedListId no longer matches the card's current list. The card moved since you read it. |
422 | validation_error | A field failed validation. The message names the problem. |
422 | invalid_label | A labelId doesn't belong to the target project. |
422 | invalid_position | The move's position is outside the valid range for the target list. |
429 | RATE_LIMITED | This key has sent more than 60 requests in the last minute. |
500 | internal_error | An unexpected server error. Nothing in the request is wrong. |
Retries and rate limits
Retry only when the error is temporary. Fix the request for everything else.
- Retry
429and500with exponential backoff. Start at about one second and double the wait on each attempt, with a fixed maximum number of tries. - Don't retry
400,401,403,404, or422without changing the request or key. Retrying the same call returns the same error. - On
409, read the card again and use its currentlistIdbefore you try the move again. - Watch the limit. Each API key allows 60 requests per minute, across all of its calls. Spread bulk work over time, or cache reads you repeat.
Tip
The API doesn't accept an idempotency key. If a POST /api/v1/cards fails with a 500 or a network error, the card may already exist. Read the board or list cards before you retry, so you don't create a duplicate.
async function withBackoff(url: string, init: RequestInit, maxAttempts = 4) {
let delay = 1000;
for (let attempt = 1; ; attempt++) {
const res = await fetch(url, init);
const retryable = res.status === 429 || res.status === 500;
if (!retryable || attempt === maxAttempts) return res;
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2;
}
}
For how keys are sent and how their limits work, see Authentication.