Error Contract
Every error response across all channels follows the same contract. The transport determines where these fields appear — JSON body for HTTP/GraphQL, trailers for gRPC, diagnostic messages for LDAP — but the shape is identical.
For the full list of error codes and their details schemas, see the generated Error Catalog. For retry strategy and error correlation, see Error Handling.
Fields
Section titled “Fields”| Field | Stability | Description |
|---|---|---|
id | Stable | Unique failure identifier (UUID) |
timestamp | Stable | When the failure occurred (ISO 8601) |
code | Stable | Machine-readable error code (e.g., ARGUMENT_INVALID_JSON) |
kind | Stable | Error classification (e.g., INVALID_ARGUMENT) |
message | Informational | Human-readable description — may change between versions, do not parse |
trace_id | Stable | OpenTelemetry trace ID (optional, when tracing is active) |
span_id | Stable | OpenTelemetry span ID (optional) |
correlation | Stable | Request correlation ID (optional, when client provides one) |
retry | Stable | Retry hint: { "after": "PT2S" } — absent when retry is not suggested |
details | Stable | Structured context specific to the error code (optional) |
Backwards compatibility: details schemas may gain new optional fields, but existing fields are never removed or renamed.
Default codes: When no explicit code is set, the code defaults to the kind name (e.g., INVALID_ARGUMENT). See the Error Catalog for all codes.
Scribe uses 16 error kinds, aligned with gRPC status codes:
| Kind | Meaning | Default Retry |
|---|---|---|
CANCELLED | Caller cancelled the operation | Never |
INVALID_ARGUMENT | Bad request parameters | Never |
OUT_OF_RANGE | Value out of range | Never |
FAILED_PRECONDITION | Wrong system state | Never |
UNAUTHENTICATED | Missing or invalid credentials | Never |
PERMISSION_DENIED | Caller lacks permission | Never |
NOT_FOUND | Resource not found | Never |
ALREADY_EXISTS | Resource already exists | Never |
CONFLICT | Conflicts with current state | Never |
RESOURCE_EXHAUSTED | Quota or capacity exceeded | After 2s |
DEADLINE_EXCEEDED | Operation timed out | After 1s |
UNAVAILABLE | Service temporarily unavailable | After 5s |
UNIMPLEMENTED | Operation not supported | Never |
INTERNAL | Internal error (invariant broken) | Never |
DATA_LOSS | Unrecoverable data corruption | Never |
UNKNOWN | Unknown or unmapped error | Never |
Per-channel formats
Section titled “Per-channel formats”Each channel carries the same error information in its native format:
| Channel | Where to find the error |
|---|---|
| HTTP/REST | JSON body under error + response headers (Error-Id, Error-Code, Retry-After) |
| GraphQL | errors[].extensions.error (same shape as HTTP) |
| gRPC | google.rpc.Status with ErrorInfo in details + metadata trailers |
| LDAP | ResultCode + diagnostic message with JSON {"code":"...","id":"..."} |
REST example:
HTTP/1.1 503 Service UnavailableError-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7Error-Code: DIRECTORY_BUSYRetry-After: 2
{ "error": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "code": "DIRECTORY_BUSY", "kind": "UNAVAILABLE", "message": "Directory service is busy. Please retry later.", "status": 503, "retry": { "after": "PT2S" } }}For full transport details (status code mappings, header/trailer formats, gRPC ErrorInfo metadata), see the REST, GraphQL, and LDAP channel docs.
Note: Some details fields (configuredBases, baseTypes, value, filter) may contain sensitive information. Deployments can omit these fields depending on their exposure policy. Clients should handle missing details gracefully.