Errors
Read Spiral error responses, branch on the right field, and decide whether to retry.
Spiral's APIs return errors in one of three JSON shapes, depending on the
service. The Catalog and Applications return a stable errorName you can
branch on; other services return a kind or a message. In every case the
HTTP status is reliable, so start there. This page shows each shape, which
routes use it, and which errors are worth retrying.
Which body a route returns
| Routes | Error body |
|---|---|
Catalog (/api/catalog/...) and Applications (/api/apps/...) | Error envelope |
Tables and Enrichments (/arbor/...), Locations, Connections, Vector Indexes, Tasks, and worker pools and sessions (/projects/{project_id}/...) | Kind body |
Organizations, Projects, Storage, workload identities, grants and installations, managed workers, and POST /oauth/token on api.spiraldb.dev | Message body |
OAuth endpoints on auth.spiraldb.com | OAuth error body: error and error_description |
Error envelope
{
"errorCode": "CONFLICT",
"errorName": "Catalog:IdempotencyKeyReused",
"errorInstanceId": "0199d5a2-7c4e-7b8a-9a51-2f0c6e8b1d42"
}| Field | Meaning |
|---|---|
errorCode | The canonical code. It selects the HTTP status. |
errorName | A stable name in the form <Service>:<Error>. Branch on this field. |
errorInstanceId | A UUIDv7 for this occurrence. The server logs the same ID. Include it when you report a problem. |
parameters | Optional. A map of the error's declared parameters. Each value is a string. |
Each operation in the HTTP reference lists the error names that it declares,
with their parameters. A server can also return names that the operation does
not declare, such as the shared authentication errors. Treat an unknown name by
its errorCode.
Codes and statuses
errorCode | Status |
|---|---|
INVALID_ARGUMENT | 400 |
UNAUTHENTICATED | 401 |
PERMISSION_DENIED | 403 |
NOT_FOUND | 404 |
CONFLICT | 409 |
FAILED_PRECONDITION | 412 |
REQUEST_ENTITY_TOO_LARGE | 413 |
INTERNAL | 500 |
UNAVAILABLE | 503 |
TIMEOUT | 504 |
Shared errors
These errors can occur on every /api route:
errorName | Status | Cause |
|---|---|---|
Default:Unauthenticated | 401 | The Authorization header is missing, or the token is not valid. |
Default:PermissionDenied | 403 | The caller does not have a permission. The operation and resource parameters name it. |
Contract:InvalidPath | 400 | A path parameter does not decode. |
Contract:InvalidQuery | 400 | A query parameter does not decode. |
Contract:InvalidRequest | 400 | The JSON body does not match the request type, contains an unknown field, or repeats a key. |
Contract:UnexpectedBody | 400 | The operation takes no request body. |
Contract:UnexpectedQuery | 400 | The operation takes no query parameters. |
Contract:UnsupportedMediaType | 415 | The Content-Type is not the media type that the operation accepts. |
Contract:RequestTooLarge | 413 | The body exceeds the size limit of the operation. |
Contract:Internal | 500 | An unexpected server failure. Only errorInstanceId identifies it. |
Internal causes stay in the server logs and never appear in the envelope, so
include errorInstanceId when you report a problem.
Kind body
These routes return a JSON object with a kind field:
{
"kind": "permissionDenied",
"operation": "table:read",
"resource": "c0a8012e-5b8f-4c1e-9d3a-6f7e8a9b0c1d"
}kind | Status | Other fields |
|---|---|---|
badRequest | 400 | message |
unauthorized | 401 | message, always Unauthorized |
permissionDenied | 403 | operation, resource |
notFound | 404 | resource |
conflict | 409 | message |
internal | 500 | message, always Internal server error |
Tasks and worker pools also return limitReached (402) and notImplemented
(501), each with message.
Message body
These routes return a JSON object with one message field:
{ "message": "Resource `proj_123` not found" }| Status | message |
|---|---|
400 | The reason that the request is not valid. |
401 | Unauthorized. The server does not return the reason. |
402 | The limit that the request reached. |
403 | Permission `<operation>` denied on `<resource>` |
404 | Resource `<resource>` not found |
409 | The reason for the conflict. |
501 | The feature that is not implemented. |
500 | Internal server error |
Malformed requests on service routes
On service routes, the server
decodes the path, the query, and the JSON body before the service runs. A decoding failure returns a plain-text body, not
JSON. The status is 400 for malformed JSON or an invalid path or query, 415
for a missing JSON Content-Type, and 422 for a JSON body that does not match
the request type.
Retry an error
Do not retry 4xx errors without a change to the request. The exceptions are
401 after a token refresh, and a conflict that you resolve first. For a write
that returned 500 or no response, retry with the same IDs; see
Retries.