Error codes
Every error response carries a code. These are part of the public contract and
covered by semantic versioning: branch on them freely. Adding a code is a minor
version; renaming or removing one is a major version.
The type URI of a problem document links to this page's entry for that code, so
following one lands on the section describing it.
| Code | HTTP | Title | Meaning |
|---|---|---|---|
cursor_expired |
410 | Cursor expired | A change-feed cursor names a point older than the events this instance still holds, so the gap between there and now cannot be reported. The client resyncs from the beginning rather than being handed a page that silently omits everything pruned in between. |
cycle_detected |
409 | Cycle detected | The change would make something its own ancestor, in a project tree, a task hierarchy, or a chain of links that say which of a pair comes first - the blocking ones, and the one that says a document replaces another. |
database_busy |
503 | Busy | Another connection held the database and this request was refused - sometimes at once and sometimes after waiting, which is why the terminal says how long its attempt took. Distinct from 'request_timed_out', which is a statement this instance stopped waiting for, and from 'service_unavailable', which says the instance cannot serve anything yet: this instance is serving and this database is working, and it was busy at that moment. The request that met it changed nothing, and trying again is the remedy; a caller that made several requests says which of them went through. |
duplicate_key |
409 | Already exists | Something with that identifying value is already here - a project key, a username, a tag name, or a document that has already been superseded. |
forbidden |
403 | Not permitted | The credential is valid but does not permit this. Where the refusal turns on a permission the caller lacks, that permission is named so they can ask for a token carrying it - but several do not: a token pinned to another workspace, a caller who is not a member, and a project scope narrower than the project reached are each about reach rather than about a verb. |
in_use |
409 | Still in use | The thing being removed is still referenced - a status some tasks are in, a link type some links use. The message says how many, so the caller can move them rather than guess. Removing a tag is deliberately not this: taking a label off the things it is on is what deleting a label means. |
internal_error |
500 | Internal error | Something failed that should not have. The detail is deliberately vague; the request id is what ties the response to the log entry that explains it. |
invalid_field_value |
422 | Invalid field value | The request was well-formed but a field's value cannot be used. The field is named and, where the valid values are a known set, they are listed. |
invalid_status |
422 | Invalid status | The status asked for cannot be used here. Usually no status with that key exists for this entity type in this workspace, and then the valid keys are listed - an installation may rename them freely, so they are read from the workspace rather than assumed. It also reports a workspace with no default status at all, where there are no keys to list and the answer is to seed it. |
malformed_request |
400 | Malformed request | The request could not be read at all - bad JSON, or a header this API has to parse and could not. A parameter of the wrong shape is a different answer: the request was read, so it is 422 'invalid_field_value' naming the parameter. |
method_not_allowed |
405 | Method not allowed | The path exists but does not answer to that HTTP method. The methods it does answer to are listed in the 'Allow' header. |
missing_field |
422 | Missing field | A field this endpoint requires was not supplied. |
not_found |
404 | Not found | There is no such thing, or it is not visible to this caller. The two are deliberately not distinguished: saying 'forbidden' about a private project would confirm it exists. |
payload_too_large |
413 | Too large | A field or the request body exceeds the configured limit. The limit is reported rather than the value being silently truncated. |
rate_limited |
429 | Too many requests | The caller is going faster than the configured limit allows. The response says when to try again. |
request_timed_out |
503 | Timed out | The database work behind this request ran longer than 'request_timeout_seconds' allows, and was given up on. Distinct from 'service_unavailable', which says the instance cannot serve anything yet: this instance is serving, and it was this request that did not finish. The detail names what was being waited for where the database said, and retrying may work. |
schema_mismatch |
409 | Schema mismatch | A database schema does not match the one this build expects. An older schema can be migrated forward and the refusal says so; a newer one cannot, because this version cannot interpret data it does not know the shape of and a partial read is worse than a clear failure. Two things answer with it: a backup being put back, and any write against a live database that has not been migrated yet - reads are still served, so an instance mid-deploy stays readable and refuses to be changed. /readyz reports the same condition as 503 service_unavailable rather than this, because a load balancer has to read the instance as not ready rather than as arguing. |
service_unavailable |
503 | Not ready | The instance is running but cannot serve requests yet - most often its database is unreachable, or its schema has not been brought up to date. Reported by the readiness check so that a deployment holds traffic back rather than serving errors. |
unauthenticated |
401 | Not authenticated | No credential was presented, or the one presented is not valid. Every reason reports identically: an unknown token, a revoked one and an expired one are indistinguishable from outside on purpose. |
unknown_field |
422 | Unknown field | The request carried a field or query parameter this endpoint does not accept. Rejected rather than ignored, because silently dropping a typo is how a caller comes to believe it set something it did not. |
unsupported_protocol_version |
400 | Unsupported protocol version | A client announced an MCP revision this server does not speak, which the Streamable HTTP transport requires be refused rather than answered as though it were understood. The revision this server does speak is named, so a client can decide whether to continue. Distinct from 'malformed_request' because the request was read perfectly well. |
version_conflict |
409 | Version conflict | The entity changed since the version the caller sent. The response carries both versions and the current entity, so the caller can merge rather than refetch and start again. |
cursor_expired
Cursor expired - HTTP 410.
A change-feed cursor names a point older than the events this instance still holds, so the gap between there and now cannot be reported. The client resyncs from the beginning rather than being handed a page that silently omits everything pruned in between.
cycle_detected
Cycle detected - HTTP 409.
The change would make something its own ancestor, in a project tree, a task hierarchy, or a chain of links that say which of a pair comes first - the blocking ones, and the one that says a document replaces another.
database_busy
Busy - HTTP 503.
Another connection held the database and this request was refused - sometimes at once and sometimes after waiting, which is why the terminal says how long its attempt took. Distinct from 'request_timed_out', which is a statement this instance stopped waiting for, and from 'service_unavailable', which says the instance cannot serve anything yet: this instance is serving and this database is working, and it was busy at that moment. The request that met it changed nothing, and trying again is the remedy; a caller that made several requests says which of them went through.
duplicate_key
Already exists - HTTP 409.
Something with that identifying value is already here - a project key, a username, a tag name, or a document that has already been superseded.
forbidden
Not permitted - HTTP 403.
The credential is valid but does not permit this. Where the refusal turns on a permission the caller lacks, that permission is named so they can ask for a token carrying it - but several do not: a token pinned to another workspace, a caller who is not a member, and a project scope narrower than the project reached are each about reach rather than about a verb.
in_use
Still in use - HTTP 409.
The thing being removed is still referenced - a status some tasks are in, a link type some links use. The message says how many, so the caller can move them rather than guess. Removing a tag is deliberately not this: taking a label off the things it is on is what deleting a label means.
internal_error
Internal error - HTTP 500.
Something failed that should not have. The detail is deliberately vague; the request id is what ties the response to the log entry that explains it.
invalid_field_value
Invalid field value - HTTP 422.
The request was well-formed but a field's value cannot be used. The field is named and, where the valid values are a known set, they are listed.
invalid_status
Invalid status - HTTP 422.
The status asked for cannot be used here. Usually no status with that key exists for this entity type in this workspace, and then the valid keys are listed - an installation may rename them freely, so they are read from the workspace rather than assumed. It also reports a workspace with no default status at all, where there are no keys to list and the answer is to seed it.
malformed_request
Malformed request - HTTP 400.
The request could not be read at all - bad JSON, or a header this API has to parse and could not. A parameter of the wrong shape is a different answer: the request was read, so it is 422 'invalid_field_value' naming the parameter.
method_not_allowed
Method not allowed - HTTP 405.
The path exists but does not answer to that HTTP method. The methods it does answer to are listed in the 'Allow' header.
missing_field
Missing field - HTTP 422.
A field this endpoint requires was not supplied.
not_found
Not found - HTTP 404.
There is no such thing, or it is not visible to this caller. The two are deliberately not distinguished: saying 'forbidden' about a private project would confirm it exists.
payload_too_large
Too large - HTTP 413.
A field or the request body exceeds the configured limit. The limit is reported rather than the value being silently truncated.
rate_limited
Too many requests - HTTP 429.
The caller is going faster than the configured limit allows. The response says when to try again.
request_timed_out
Timed out - HTTP 503.
The database work behind this request ran longer than 'request_timeout_seconds' allows, and was given up on. Distinct from 'service_unavailable', which says the instance cannot serve anything yet: this instance is serving, and it was this request that did not finish. The detail names what was being waited for where the database said, and retrying may work.
schema_mismatch
Schema mismatch - HTTP 409.
A database schema does not match the one this build expects. An older schema can be migrated forward and the refusal says so; a newer one cannot, because this version cannot interpret data it does not know the shape of and a partial read is worse than a clear failure. Two things answer with it: a backup being put back, and any write against a live database that has not been migrated yet - reads are still served, so an instance mid-deploy stays readable and refuses to be changed. /readyz reports the same condition as 503 service_unavailable rather than this, because a load balancer has to read the instance as not ready rather than as arguing.
service_unavailable
Not ready - HTTP 503.
The instance is running but cannot serve requests yet - most often its database is unreachable, or its schema has not been brought up to date. Reported by the readiness check so that a deployment holds traffic back rather than serving errors.
unauthenticated
Not authenticated - HTTP 401.
No credential was presented, or the one presented is not valid. Every reason reports identically: an unknown token, a revoked one and an expired one are indistinguishable from outside on purpose.
unknown_field
Unknown field - HTTP 422.
The request carried a field or query parameter this endpoint does not accept. Rejected rather than ignored, because silently dropping a typo is how a caller comes to believe it set something it did not.
unsupported_protocol_version
Unsupported protocol version - HTTP 400.
A client announced an MCP revision this server does not speak, which the Streamable HTTP transport requires be refused rather than answered as though it were understood. The revision this server does speak is named, so a client can decide whether to continue. Distinct from 'malformed_request' because the request was read perfectly well.
version_conflict
Version conflict - HTTP 409.
The entity changed since the version the caller sent. The response carries both versions and the current entity, so the caller can merge rather than refetch and start again.