Failures
Handle errors consistently across all IdentityScribe channels. This reference covers error response parsing, retry logic, user-friendly messages, and troubleshooting via logs and traces.
Related:
- Upgrading IdentityScribe — Upgrade notes for breaking error-code changes
- REST Channel — Error envelopes and HTTP semantics
- Observability — Correlate
Error-Idwith traces and metrics
Overview
Section titled “Overview”Every error can be represented by these fields. Where they appear depends on the transport: body fields (HTTP/GraphQL), headers/trailers (HTTP/gRPC), or diagnostic messages (LDAP).
| Field | Type | Presence | Stability | Description |
|---|---|---|---|---|
id | string (UUID) | Always | Stable | Unique identifier for this failure instance |
timestamp | string (ISO 8601) | Always | Stable | When the failure occurred |
trace_id | string | Optional | Stable | OpenTelemetry trace ID (32 hex chars) where the failure was created |
span_id | string | Optional | Stable | OpenTelemetry span ID (16 hex chars) where the failure was created |
code | string | Always | Stable | Machine-readable error code (e.g., ARGUMENT_INVALID_JSON) |
kind | string | Always | Stable | Error classification (e.g., INVALID_ARGUMENT) |
message | string | Always | Informational | Human-readable description (may change between versions) |
correlation | string | Optional | Stable | Request correlation ID (when provided by client) |
retry | object | Optional | Stable | Retry hint: { "after": "PT2S" } or { "at": "..." }. Absent when retry is not suggested. |
details | object | Optional | Stable | Structured context specific to the error code. Absent for generic failures. |
Default codes: When no explicit code is set, the default is the kind name (e.g., INVALID_ARGUMENT). For the canonical list of codes, see the generated Error Catalog.
Field contract
Section titled “Field contract”Stable fields (parse these)
Section titled “Stable fields (parse these)”Clients should parse and act on these fields:
id: Unique failure identifier for correlation with logs/tracestimestamp: When the failure occurred (ISO 8601)trace_id: OpenTelemetry trace ID for distributed tracing correlation (optional, present when tracing is active)span_id: OpenTelemetry span ID for distributed tracing correlation (optional, present when tracing is active)code: Stable error code for programmatic handling (switch/match statements)kind: Error classification for category-level handlingcorrelation: Request correlation ID (optional, present when client provides one)retry: Machine-readable retry semantics (optional, present when retry is suggested)details: Structured context (optional, schema is stable per error code when present)
Human-readable field (informational)
Section titled “Human-readable field (informational)”message: User-facing description derived from the error. May change between versions; do not parse programmatically.
Backwards compatibility
Section titled “Backwards compatibility”For details schemas:
- Additive fields allowed: New optional fields may be added
- No removals: Existing fields are never removed
- No renames: Field names are stable
- Semantics preserved: Field meanings do not change
Error kind reference
Section titled “Error kind reference”IdentityScribe uses 16 error kinds aligned with gRPC status codes:
| Kind | Meaning | Default Retry |
|---|---|---|
CANCELLED | Operation was cancelled (typically by the caller) | Never |
INVALID_ARGUMENT | Invalid request parameters (malformed or semantically invalid) | Never |
OUT_OF_RANGE | Requested value is out of range | Never |
FAILED_PRECONDITION | Operation not allowed in the current state | Never |
UNAUTHENTICATED | Missing or invalid authentication credentials | Never |
PERMISSION_DENIED | Caller does not have permission | Never |
NOT_FOUND | Requested resource was not found | Never |
ALREADY_EXISTS | Resource already exists | Never |
CONFLICT | Request conflicts with current resource state | Never |
RESOURCE_EXHAUSTED | Quota, limit, or capacity exhausted | After 2s |
DEADLINE_EXCEEDED | Operation timed out | After 1s |
UNAVAILABLE | Service temporarily unavailable | After 5s |
UNIMPLEMENTED | Operation not implemented or supported | Never |
INTERNAL | Internal error (invariant broken) | Never |
DATA_LOSS | Unrecoverable data loss or corruption | Never |
UNKNOWN | Unknown or unmapped error | Never |
Transport envelopes
Section titled “Transport envelopes”HTTP / REST
Section titled “HTTP / REST”Status code mapping
Section titled “Status code mapping”| Kind | HTTP Status |
|---|---|
CANCELLED | 499 (Client Closed Request) |
INVALID_ARGUMENT | 400 (Bad Request) |
OUT_OF_RANGE | 400 (Bad Request) |
FAILED_PRECONDITION | 409 (Conflict) |
UNAUTHENTICATED | 401 (Unauthorized) |
PERMISSION_DENIED | 403 (Forbidden) |
NOT_FOUND | 404 (Not Found) |
ALREADY_EXISTS | 409 (Conflict) |
CONFLICT | 409 (Conflict) |
RESOURCE_EXHAUSTED | 429 (Too Many Requests) |
DEADLINE_EXCEEDED | 504 (Gateway Timeout) |
UNAVAILABLE | 503 (Service Unavailable) |
UNIMPLEMENTED | 501 (Not Implemented) |
INTERNAL | 500 (Internal Server Error) |
DATA_LOSS | 500 (Internal Server Error) |
UNKNOWN | 500 (Internal Server Error) |
Response headers
Section titled “Response headers”| Header | Description |
|---|---|
Error-Id | Unique failure identifier (UUID) |
Error-Code | Stable error code |
Error-Kind | Error classification |
Correlation-Id | Request correlation ID (if present) |
Trace-Id | OpenTelemetry trace ID (32 hex chars, if tracing active) |
Span-Id | OpenTelemetry span ID (16 hex chars, if tracing active) |
Retry-After | Retry delay in seconds or HTTP-date |
Naming Convention: HTTP headers use
Title-Case(Trace-Id,Span-Id) per HTTP conventions, while JSON body fields usesnake_case(trace_id,span_id) to match OpenTelemetry and logging conventions.
Response body schema
Section titled “Response body schema”The response body is wrapped in an error object. The error.status field records the HTTP status that would have been
returned (useful for streaming/GraphQL cases where the HTTP status may already be committed as 200).
{ "error": { "id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-01-07T10:30:00Z", "trace_id": "0af7651916cd43dd8448eb211c80319c", "span_id": "b7ad6b7169203331", "code": "ARGUMENT_INVALID_JSON", "kind": "INVALID_ARGUMENT", "message": "Invalid JSON format for 'filter'.", "status": 400, "details": { "location": "query", "name": "filter", "reason": "Invalid JSON syntax" } }}Example: invalid argument
Section titled “Example: invalid argument”HTTP/1.1 400 Bad RequestContent-Type: application/jsonError-Id: 550e8400-e29b-41d4-a716-446655440000Error-Code: ARGUMENT_INVALID_JSONError-Kind: INVALID_ARGUMENT
{ "error": { "id": "550e8400-e29b-41d4-a716-446655440000", "code": "ARGUMENT_INVALID_JSON", "kind": "INVALID_ARGUMENT", "message": "Invalid JSON format for 'filter'.", "status": 400, "details": { "location": "query", "name": "filter", "reason": "Invalid JSON syntax", "value": "{invalid" } }}Example: service busy (with retry)
Section titled “Example: service busy (with retry)”HTTP/1.1 503 Service UnavailableContent-Type: application/jsonError-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7Error-Code: DIRECTORY_BUSYError-Kind: UNAVAILABLERetry-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" }, "details": { "permitsRequested": 1, "permitsAvailable": 0, "queueLength": 3, "waitTimeMs": 5000 } }}GraphQL
Section titled “GraphQL”GraphQL responses use the GraphQL errors array. To keep error handling consistent across channels, failure details are
carried under extensions.error using the same shape as the HTTP error object:
{ "errors": [ { "message": "Invalid JSON format for 'filter'.", "extensions": { "error": { "id": "550e8400-e29b-41d4-a716-446655440000", "code": "ARGUMENT_INVALID_JSON", "kind": "INVALID_ARGUMENT", "message": "Invalid JSON format for 'filter'.", "status": 400, "details": { "location": "query", "name": "filter", "reason": "Invalid JSON syntax" } } } } ]}Note: The GraphQL top-level
messagemay be redundant; clients should treatextensions.error.code/.kindas the stable contract.
Status code mapping
Section titled “Status code mapping”| Kind | gRPC Status |
|---|---|
CANCELLED | CANCELLED |
INVALID_ARGUMENT | INVALID_ARGUMENT |
OUT_OF_RANGE | OUT_OF_RANGE |
FAILED_PRECONDITION | FAILED_PRECONDITION |
UNAUTHENTICATED | UNAUTHENTICATED |
PERMISSION_DENIED | PERMISSION_DENIED |
NOT_FOUND | NOT_FOUND |
ALREADY_EXISTS | ALREADY_EXISTS |
CONFLICT | ABORTED |
RESOURCE_EXHAUSTED | RESOURCE_EXHAUSTED |
DEADLINE_EXCEEDED | DEADLINE_EXCEEDED |
UNAVAILABLE | UNAVAILABLE |
UNIMPLEMENTED | UNIMPLEMENTED |
INTERNAL | INTERNAL |
DATA_LOSS | DATA_LOSS |
UNKNOWN | UNKNOWN |
Metadata trailers
Section titled “Metadata trailers”| Trailer | Presence | Description |
|---|---|---|
error-id | Default | Unique failure identifier (UUID) |
error-code | Default | Stable error code |
correlation-id | Default | Request correlation ID (when provided by client) |
trace-id | Default | OpenTelemetry trace ID (32 hex chars, if tracing active) |
span-id | Default | OpenTelemetry span ID (16 hex chars, if tracing active) |
retry-after | Default | Retry delay in seconds or RFC 1123 date (when retry is suggested) |
error-kind | Optional | Error classification (not included by default) |
google.rpc.Status Details
Section titled “google.rpc.Status Details”The response includes google.rpc.Status with:
ErrorInfo: Always present. Containsreason(kind name),domain(k5), and metadata:errorCode: Always includederrorDetails: Optional (not included by default; requires explicit configuration)
RetryInfo: Present when retry is suggested
Example
Section titled “Example”Status { code: 3 // INVALID_ARGUMENT message: "Invalid JSON format for 'filter'." details: [ ErrorInfo { reason: "INVALID_ARGUMENT" domain: "k5" metadata: { "errorCode": "ARGUMENT_INVALID_JSON" } } ]}ResultCode mapping
Section titled “ResultCode mapping”| Kind | LDAP ResultCode |
|---|---|
CANCELLED | CANCELED (118) |
INVALID_ARGUMENT | PROTOCOL_ERROR (2) |
OUT_OF_RANGE | CONSTRAINT_VIOLATION (19) |
FAILED_PRECONDITION | CONSTRAINT_VIOLATION (19) |
UNAUTHENTICATED | INVALID_CREDENTIALS (49) |
PERMISSION_DENIED | INSUFFICIENT_ACCESS_RIGHTS (50) |
NOT_FOUND | NO_SUCH_OBJECT (32) |
ALREADY_EXISTS | ENTRY_ALREADY_EXISTS (68) |
CONFLICT | BUSY (51) |
RESOURCE_EXHAUSTED | BUSY (51) |
DEADLINE_EXCEEDED | TIME_LIMIT_EXCEEDED (3) |
UNAVAILABLE | UNAVAILABLE (52) |
UNIMPLEMENTED | UNWILLING_TO_PERFORM (53) |
INTERNAL | OTHER (80) |
DATA_LOSS | OTHER (80) |
UNKNOWN | OTHER (80) |
Code-specific overrides
Section titled “Code-specific overrides”Some error codes map to more specific LDAP result codes:
| Error Code | LDAP ResultCode |
|---|---|
DIRECTORY_SIZE_LIMIT_EXCEEDED | SIZE_LIMIT_EXCEEDED (4) |
DIRECTORY_BUSY | BUSY (51) |
Diagnostic message format
Section titled “Diagnostic message format”LDAP diagnostic messages include the error code and failure ID:
Directory service is busy. Please retry later. {"code":"DIRECTORY_BUSY","id":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}Error codes and details schemas
Section titled “Error codes and details schemas”Argument validation (ARGUMENT_*)
Section titled “Argument validation (ARGUMENT_*)”These errors indicate invalid request parameters.
| Code | Kind | Description |
|---|---|---|
ARGUMENT_INVALID_JSON | INVALID_ARGUMENT | Invalid JSON syntax |
ARGUMENT_INVALID_TYPE | INVALID_ARGUMENT | Wrong JSON value type (e.g., expected array, got object) |
ARGUMENT_INVALID_ELEMENT | INVALID_ARGUMENT | Invalid element type in array |
ARGUMENT_MISSING_FIELD | INVALID_ARGUMENT | Required field missing |
ARGUMENT_MUTUALLY_EXCLUSIVE | INVALID_ARGUMENT | Mutually exclusive parameters specified together |
ARGUMENT_INVALID_VALUE | INVALID_ARGUMENT | Invalid value format |
Details schema
Section titled “Details schema”{ location?: string; // Where the argument appears: "query", "header", "body" name?: string; // Parameter name reason?: string; // Human-readable reason value?: string; // Truncated input value (max 200 chars)}Example
Section titled “Example”{ "error": { "code": "ARGUMENT_INVALID_TYPE", "kind": "INVALID_ARGUMENT", "message": "Parameter 'include' must be array.", "status": 400, "details": { "location": "query", "name": "include", "reason": "Expected ARRAY, got OBJECT" } }}Filter parsing (FILTER_*)
Section titled “Filter parsing (FILTER_*)”These errors indicate problems parsing filter expressions (LDAP, SCIM, or JSON format).
| Code | Kind | Description |
|---|---|---|
FILTER_INVALID_SYNTAX | INVALID_ARGUMENT | General parse failure |
FILTER_UNSUPPORTED_PATH | INVALID_ARGUMENT | SCIM attribute path not supported (e.g., dotted paths) |
FILTER_UNSUPPORTED_FEATURE | INVALID_ARGUMENT | Unsupported filter feature |
FILTER_NESTING_EXCEEDED | INVALID_ARGUMENT | Filter nesting depth exceeds limit |
FILTER_TOO_LARGE | INVALID_ARGUMENT | Filter string exceeds maximum length |
Details schema
Section titled “Details schema”{ format?: string; // Filter format: "JSON", "SCIM", "LDAP" filter?: string; // Truncated filter string (max 200 chars) reason?: string; // Human-readable reason}Example
Section titled “Example”{ "error": { "code": "FILTER_UNSUPPORTED_PATH", "kind": "INVALID_ARGUMENT", "message": "Unsupported attribute path in filter.", "status": 400, "details": { "format": "SCIM", "filter": "name.familyName eq \"Smith\"", "reason": "SCIM attribute path 'name.familyName' is not supported. Only simple attribute names are allowed." } }}Directory operations (DIRECTORY_*)
Section titled “Directory operations (DIRECTORY_*)”These errors are specific to directory service operations.
DIRECTORY_BUSY
Section titled “DIRECTORY_BUSY”| Field | Value |
|---|---|
| Kind | UNAVAILABLE |
| Default Retry | After 2 seconds |
| Description | Directory service is overloaded |
Details Schema:
{ permitsRequested: number; // Permits the query tried to acquire permitsAvailable: number; // Permits available at request time queueLength: number; // Threads waiting for permits when rejected waitTimeMs: number; // How long the request waited before failing}The queueLength field indicates how many other threads were waiting for permits when this request was rejected. High values suggest contention from slow queries holding permits.
DIRECTORY_SIZE_LIMIT_EXCEEDED
Section titled “DIRECTORY_SIZE_LIMIT_EXCEEDED”| Field | Value |
|---|---|
| Kind | RESOURCE_EXHAUSTED |
| Default Retry | Never |
| Description | Query result exceeded configured size limit |
Details Schema:
{ sizeLimit: number; // Configured size limit emitted: number; // Number of entries emitted before limit phase: string; // Processing phase where limit was hit}DIRECTORY_TIME_LIMIT_EXCEEDED
Section titled “DIRECTORY_TIME_LIMIT_EXCEEDED”| Field | Value |
|---|---|
| Kind | DEADLINE_EXCEEDED |
| Default Retry | After 1 second |
| Description | Query exceeded configured time limit |
Details Schema:
{ timeLimit: string; // Configured time limit (ISO 8601 duration) phase: string; // Processing phase where limit was hit}DIRECTORY_UNSUPPORTED_ATTRIBUTE_OP
Section titled “DIRECTORY_UNSUPPORTED_ATTRIBUTE_OP”| Field | Value |
|---|---|
| Kind | INVALID_ARGUMENT |
| Default Retry | Never |
| Description | Query uses an unsupported operation for an attribute |
Details Schema:
{ attributeOriginal: string; // Attribute name as specified in query attributeNormalized: string; // Normalized attribute name operation: string; // Unsupported operation: "SUBSTRING", "APPROXIMATE", "ORDERING", "SORT"}DIRECTORY_OUTSIDE_ALL_BASES
Section titled “DIRECTORY_OUTSIDE_ALL_BASES”| Field | Value |
|---|---|
| Kind | OUT_OF_RANGE |
| Default Retry | Never |
| Description | Query base DN is outside all configured directory scopes |
Details Schema:
{ base: string; // Requested base DN configuredBases: string[]; // Configured base DNs}Note: This response may reveal internal directory structure. Consider your security model when exposing
configuredBasesto external clients.
DIRECTORY_MISSING_TYPE_CONSTRAINT
Section titled “DIRECTORY_MISSING_TYPE_CONSTRAINT”| Field | Value |
|---|---|
| Kind | INVALID_ARGUMENT |
| Default Retry | Never |
| Description | Query base is too broad without an explicit type constraint |
Details Schema:
{ base: string; // Requested base DN candidates: string[]; // Type candidates that match the base}DIRECTORY_INCONSISTENT_TYPE_CONSTRAINT
Section titled “DIRECTORY_INCONSISTENT_TYPE_CONSTRAINT”| Field | Value |
|---|---|
| Kind | INVALID_ARGUMENT |
| Default Retry | Never |
| Description | Query constraints are inconsistent (base types vs filter types) |
Details Schema:
{ base: string; // Requested base DN baseTypes: string[]; // Types allowed by the base intentTypes: string[]; // Types restricted by the filter}Retry guidance
Section titled “Retry guidance”When to retry
Section titled “When to retry”- Check
retryfield first: If present, use the specified delay - Check
Retry-Afterheader: For HTTP, this header provides the delay - Check
kind: Only retry for transient kinds:RESOURCE_EXHAUSTED(rate limiting)DEADLINE_EXCEEDED(timeout)UNAVAILABLE(service unavailable)
Retry strategy
Section titled “Retry strategy”if error.retry.after: wait(error.retry.after) retry()elif error.kind in [RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED, UNAVAILABLE]: wait(exponential_backoff()) retry()else: fail_permanently()Do not retry
Section titled “Do not retry”INVALID_ARGUMENT,OUT_OF_RANGE: Fix the requestUNAUTHENTICATED: Re-authenticatePERMISSION_DENIED: Request accessNOT_FOUND: Resource doesn’t existALREADY_EXISTS,CONFLICT: Resolve conflictUNIMPLEMENTED: Feature not availableINTERNAL,DATA_LOSS,UNKNOWN: Contact support
Logging and observability
Section titled “Logging and observability”When logging errors, include these fields for correlation:
| Field | Log Key | Purpose |
|---|---|---|
id | error_id | Correlate with traces and support tickets |
code | error_code | Aggregate errors by type |
kind | error_kind | Aggregate errors by category |
correlation | correlation_id | Trace requests across services |
trace_id | trace_id | OpenTelemetry trace ID for distributed tracing |
span_id | span_id | OpenTelemetry span ID for distributed tracing |
Example log entry
Section titled “Example log entry”{ "level": "ERROR", "message": "Request failed", "error_id": "550e8400-e29b-41d4-a716-446655440000", "error_code": "DIRECTORY_BUSY", "error_kind": "UNAVAILABLE", "correlation_id": "req-12345", "trace_id": "0af7651916cd43dd8448eb211c80319c", "span_id": "b7ad6b7169203331"}Security considerations
Section titled “Security considerations”Some details fields may contain sensitive information:
configuredBases,baseTypes,intentTypes: May reveal internal directory structurevalue,filter: May contain user input
Important: Details schemas are stable when present. However, deployments may omit sensitive fields or suppress details entirely depending on exposure policy. Clients should handle missing details gracefully.
Maintenance
Section titled “Maintenance”When adding a new error code:
- Define the code: Add it to
ScribeErrorCodesusingSCREAMING_SNAKE_CASEwithout prefix (e.g.,ARGUMENT_INVALID_JSON,FILTER_INVALID_SYNTAX,DIRECTORY_BUSY) - Choose a kind: Select from the 16 available kinds
- Define details schema: Document all fields with types and descriptions
- Set retry semantics: Determine if the error is transient
- Update this guide: Add the code to the appropriate section
- Update the ADR: If the error represents a significant change to the error model