diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index 769c82c22d..bc0c02d897 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -666,17 +666,41 @@ field-anchored". ### Error Codes -| Code | HTTP | Category | Retryable | Description | -|:-----|:-----|:---------|:----------|:------------| -| `VALIDATION_ERROR` | 400 | validation | No | Input validation failed | -| `INVALID_QUERY` | 400 | validation | No | Malformed query expression | -| `UNAUTHENTICATED` | 401 | authentication | No | Authentication required | -| `PERMISSION_DENIED` | 403 | authorization | No | Insufficient permissions | -| `RESOURCE_NOT_FOUND` | 404 | not_found | No | Resource does not exist | -| `RATE_LIMIT_EXCEEDED` | 429 | rate_limit | Yes | Too many requests | -| `INTERNAL_ERROR` | 500 | server | Yes | Unexpected server error | -| `SERVICE_UNAVAILABLE` | 503 | server | Yes | Service temporarily unavailable | -| `NOT_IMPLEMENTED` | 501 | server | No | Service not installed (plugin missing) | +`error.code` is drawn from a **two-tier vocabulary** (ADR-0112), and +`packages/spec` is the authority for the full set: + +- **Standard catalog** — the closed `StandardErrorCode` enum + (`packages/spec/src/api/errors.zod.ts`): generic conditions with + platform-wide HTTP semantics. It does not grow when a service invents a code. +- **Ledger-registered codes** — the service-specific codes each package + registers in `ERROR_CODE_LEDGER` + (`packages/spec/src/api/error-code-ledger.zod.ts`). + +The exported `ErrorCode` schema is the **union** of the two, so a code being +absent from `StandardErrorCode` does not make it unofficial or unsupported: +`VALIDATION_FAILED` — the code the examples above branch on — is a +ledger-registered code, as are several others this page's examples use. The +table below is a hand-picked subset of the codes you are most likely to branch +on, **not** either tier in full; the **Tier** column says which set a row comes +from. + +| Code | Tier | HTTP | Category | Retryable | Description | +|:-----|:-----|:-----|:---------|:----------|:------------| +| `VALIDATION_ERROR` | standard | 400 | validation | No | The **request** was refused before any record was validated — a repeated query parameter, a filter outside the allowlist, a malformed argument | +| `VALIDATION_FAILED` | ledger | 400 | validation | No | A **record** failed validation on a write; carries `fields[]`. This — not `VALIDATION_ERROR` — is what a per-field failure arrives as | +| `INVALID_QUERY` | standard | 400 | validation | No | Malformed query expression | +| `UNAUTHENTICATED` | standard | 401 | authentication | No | Authentication required | +| `PERMISSION_DENIED` | standard | 403 | authorization | No | Insufficient permissions | +| `RESOURCE_NOT_FOUND` | standard | 404 | not_found | No | Resource does not exist | +| `RATE_LIMIT_EXCEEDED` | standard | 429 | rate_limit | Yes | Too many requests | +| `INTERNAL_ERROR` | standard | 500 | server | Yes | Unexpected server error | +| `SERVICE_UNAVAILABLE` | standard | 503 | server | Yes | Service temporarily unavailable | +| `NOT_IMPLEMENTED` | standard | 501 | server | No | Service not installed (plugin missing) | + +`Category` and `Retryable` above are the **semantic** classification of each +condition. Neither is guaranteed on the wire — as the narrowing example shows, +`error.category` and `error.retryable` are present only when the server sent +them, and the REST server's per-field validation envelope sends neither. ---