Errors

The API uses conventional HTTP status codes: 2xx means your request succeeded, 4xx means something about the request needs to change, and 5xx means the failure was on our side.

Errors are RFC 9457 problem documents with a stable machine code and a traceId you can quote to support (also on the X-Trace-Id header):

{
  "title": "Group task fields are derived",
  "status": 400,
  "detail": "Cannot set progress or status on a group task directly.",
  "code": "parent_task_rollup",
  "traceId": "6e0f…"
}

Match on code, never on the title text or the HTTP status alone — title is a human-readable summary and may change; code is the stable contract.

Response codes

StatusMeaning
200 OKRequest succeeded.
201 CreatedA resource (task, dependency, resource) was created.
204 No ContentRequest succeeded; there’s nothing to return (deletes).
400 Bad RequestThe request body or parameters failed validation — see code.
401 UnauthorizedMissing, wrong, or revoked token.
403 ForbiddenAuthenticated, but not allowed to do this — see code.
404 Not FoundThe resource doesn’t exist, or you can’t reach it (the API never distinguishes the two).
409 ConflictA concurrent write or a reused Idempotency-Key with a different body.
429 Too Many RequestsRate limit or daily quota exceeded — see Rate limits & quotas →.
5xxSomething failed on our side. Safe to retry with backoff.

Error codes

CodeStatusMeaning
forbidden403ReadOnly token attempting a write, or writing to a project you can’t write to
project_read_only403the project is locked (lapsed / admin lock / pending deletion) — reason in detail
user_agent_required403the request had no (or an empty) User-Agent header — see Authentication →
missing_argument400a required field (e.g. name on create) was absent
invalid_date / invalid_dates400unparseable date / end before start
invalid_color400color must be #RRGGBB
invalid_assignee400an assignee id isn’t a member or resource of the project
invalid_parent400parent in another project, self-parent, or a hierarchy cycle
parent_task_rollup400direct progress/status on a group task
too_deep400delete would cascade through more than 20 levels
too_many_items400a reorder batch over 1,000 items, or a dependency set over the per-task cap
invalid_dependency / invalid_lag / invalid_dependency_type / cross_project / not_siblings / cycle_detected / graph_too_large400dependency graph rules
conflict409concurrent modification lost — re-read and retry
idempotency_conflict409an Idempotency-Key was reused with a different body
rate_limited / quota_exceeded429see Rate limits & quotas → — honor Retry-After

Two edge cases don’t carry a code: a malformed JSON body or wrong-typed field is rejected by the request binder as a bare 400 (validation problem document, no code), and anything you can’t reach is a plain 404 with no body detail — by design.

Idempotency and caching

POST endpoints that create things accept an Idempotency-Key header — replaying the same key and body returns the original response instead of creating a duplicate; the same key with a different body is 409 idempotency_conflict. Heavy GETs return a strong ETag; send it back as If-None-Match to get a cheap 304 Not Modified when nothing changed. See Rate limits & quotas → for both in full.

Handling errors

  • Check the code field, not the HTTP status text — the same status (e.g. 400) covers several distinct codes.
  • Retry 429s with backoff, honoring the Retry-After header rather than a fixed delay.
  • Don’t retry 4xx other than 429 without changing the request — the request itself needs to change first.
  • 5xx is always safe to retry with exponential backoff; if it persists, quote the traceId to support.