Errors
The stable error-code catalogue, what each one means, and which ones are worth branching on.
Every failure carries a machine-readable error.code alongside the HTTP status:
{
"success": false,
"message": "Moving this task to Done is not allowed from In Review.",
"error": {
"code": "INVALID_TRANSITION",
"details": { "from": "in_review", "to": "done" }
}
}Branch on error.code, never on error.message. Codes are stable and are
never reused or repurposed; messages are written for humans and get reworded.
Treat an unknown code as a generic failure of its HTTP status — that is what
makes adding a code a non-breaking change.
Codes exist because every failure used to collapse into ERROR or
VALIDATION_ERROR, and a client could not tell a rejected workflow transition
from a malformed field, or a "not a member" 403 from a "wrong role" 403.
Generic
| Code | Status | Meaning |
|---|---|---|
ERROR | 4xx/5xx | Unclassified failure |
VALIDATION_ERROR | 400 | Malformed body. details carries per-field errors |
NOT_FOUND | 404 | No such resource, or one you cannot see |
CONFLICT | 409 | The request contradicts current state |
A resource you lack permission to see returns NOT_FOUND, not FORBIDDEN.
Telling you a task exists but is out of reach is itself a disclosure — and on
a workspace with guest access it is one that matters.
Authentication and authorization
| Code | Status | Meaning |
|---|---|---|
TOKEN_INVALID | 401 | Malformed or unknown token |
TOKEN_EXPIRED | 401 | Past its mandatory expiry |
TOKEN_REVOKED | 401 | Revoked. One-way — mint a new one |
WRONG_TENANT_HOST | 401 | Valid token, wrong workspace host |
INSUFFICIENT_SCOPE | 403 | Endpoint is published; the token lacks the scope |
INSUFFICIENT_ROLE | 403 | Scope present; the service account's role is too low |
FORBIDDEN | 403 | Endpoint is not published to tokens at all |
NOT_PROJECT_MEMBER | 403 | Not a member of that project |
NOT_ORG_ADMIN | 403 | Requires organization owner or admin |
NOT_RESOURCE_OWNER | 403 | Only the author may do this |
GUEST_NOT_ALLOWED | 403 | Org-level surface, and the caller is an external guest |
RATE_LIMITED | 429 | Slow down. Carries Retry-After |
Concurrency
| Code | Status | Meaning |
|---|---|---|
STALE_WRITE | 409 | The record changed while you were editing. details carries current state to merge against |
VERSION_REQUIRED | 400 | This deployment requires a version on the write |
Task dependencies
| Code | Status | Meaning |
|---|---|---|
DEPENDENCY_CYCLE | 400 | The link would close a loop. details.path names the tasks in the cycle |
SELF_LINK | 400 | A task cannot depend on itself |
DUPLICATE_LINK | 400 | That link already exists |
INVALID_LINK_TYPE | 400 | Unknown link type |
Workflow
| Code | Status | Meaning |
|---|---|---|
INVALID_TRANSITION | 400 | Not an allowed move from the current status |
Retrying
Retry 429 and 5xx; do not retry 4xx unchanged, since the same request will
fail the same way. Use exponential backoff, and honour Retry-After when it is
present.
If a retry might duplicate a create, attach an
Idempotency-Key — that is exactly the case it
exists for.
Pagination
Offset pagination for anything a human reads, cursor pagination for anything that walks every row — and why the two behave differently on purpose.
Rate limits
Two tiers — a per-token burst limit and a per-organization sustained limit — plus the headers that let you back off on the one that is actually binding.