14 problem types the API can return
Error catalogue
Every error is RFC 9457 problem+json, and its type is a link to one of the 14 pages below.
The body shape, how to branch on it and the retry rules are on errors and retries.
The codes
| code | status | when you see it |
|---|---|---|
| account_too_large | 422 | The account holds more rows than the API will walk in one request (20,000 per table). |
| conflict | 409 | An Idempotency-Key was reused with a different body, or a request with that key is still in flight. Also: POST /v1/keys when the account already has 25 active access keys, and POST /v1/inbox/{id}/move on an image or file capture that is still pending (its bytes are only on the device that shared it), or on a capture that was already filed or archived (filing it again would make a duplicate). |
| duplicate_id | 409 | You supplied an id on create and a row with it already exists (in your account, or anywhere: row ids are unique across accounts). |
| forbidden_scope | 403 | The credential is valid but was not granted the scope this route needs. Also: an app session (a first-party JWT) on a content route: the server never receives the encryption key with one, so it cannot read or write content for it. |
| grant_expired | 401 | The grant reached its expiry. Personal access tokens expire after at most a year; OAuth grants need re-approval after 365 days. |
| grant_revoked | 401 | The grant behind this credential was disconnected. The user did it in the app (settings → ai connections or api keys), or changed their password, or changed their encryption key. |
| internal | 500 | Something failed on the server that was not your request’s fault. |
| not_found | 404 | No row with that id belongs to this user, or it was permanently deleted. |
| payload_too_large | 413 | The request body is over 4 MB. On POST /v1/attachments/upload and POST /v1/inbox/upload with the file as the raw body: the file is over 50 MB, the apps’ own limit (refused from its Content-Length, before it is read). |
| rate_limited | 429 | A rate bucket is exhausted. |
| tag_exists | 409 | You renamed a tag to a name another tag already has. That rename is a merge, and a merge has to be asked for. |
| unauthorized | 401 | The Authorization header is missing, malformed, expired, or names a credential this server does not know. |
| unsupported_media_type | 415 | A request that carries a body has no Content-Type: application/json. A GET or a DELETE sends no body and never needs the header. The two upload routes, POST /v1/attachments/upload and POST /v1/inbox/upload, are the exception: they also take a file as the raw body, of any type. |
| validation_failed | 422 | A field is missing, the wrong type, too long, or not recognised. Unknown fields are rejected rather than ignored. Lengths are counted in UTF-8 bytes (an emoji is 4), dates must be real calendar days, links must be http(s), and a title or name cannot be blank. It is also the answer to a request that is well-formed but not allowed: a move the hierarchy refuses (into a subproject, or a project that holds subprojects into a project), an attachment on a parent that does not exist or is in the trash, an image or file capture moved again after it was filed or deleted (it holds no file any more), filter criteria the grammar does not accept, a recurrence rule the engine cannot read, a move anchor (after/before) that is not in the destination list, or a parent that does not exist. |