From 447fcf93ca0e5c81ba7aea57e0a2d4ac21377c9e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 05:16:56 +0000 Subject: [PATCH] docs(api): the 409 unique-constraint entry is UNIQUE_VIOLATION on the wire (#15631) `content/docs/api/error-catalog.mdx` catalogued `DUPLICATE_RECORD` under `## Conflict Errors (409)` and in the HTTP Status Quick Reference. Per the maintainer ruling on #14723 (2026-09-03) a unique-constraint refusal has ONE wire spelling on every route, `UNIQUE_VIOLATION`; `DuplicateRecordError.code` stays `DUPLICATE_RECORD` in-process only, translated at the REST door (`packages/rest/src/error-response.ts`, the `DuplicateRecordError` arm of `structuredCodeAnswer`). A client branching on the catalogued constant never matched. The entry is renamed to the wire code with a one-sentence cross-reference to the in-process spelling, and the quick-reference row follows. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- content/docs/api/error-catalog.mdx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/content/docs/api/error-catalog.mdx b/content/docs/api/error-catalog.mdx index dc208c30e3..25772cfdbd 100644 --- a/content/docs/api/error-catalog.mdx +++ b/content/docs/api/error-catalog.mdx @@ -352,11 +352,16 @@ result set — a response indistinguishable from a successful query. **Fix:** Delete or reassign dependent records first, then retry the delete. **Retry:** `no_retry` -### `DUPLICATE_RECORD` +### `UNIQUE_VIOLATION` **Cause:** A record with the same unique key already exists. **Fix:** Update the existing record instead, or use a different unique key value. **Retry:** `no_retry` +The engine throws `DuplicateRecordError`, whose in-process `code` is +`DUPLICATE_RECORD`; the REST door translates that envelope at the boundary, so +every route answers the wire code `UNIQUE_VIOLATION` and the in-process spelling +never crosses HTTP. + ### `LOCK_CONFLICT` **Cause:** The record is locked by another process or user. **Fix:** Wait for the lock to be released, or contact the lock holder. @@ -793,7 +798,7 @@ async function handleApiCall() { | 401 | `authentication` | `UNAUTHENTICATED`, `EXPIRED_TOKEN`, `INVALID_CREDENTIALS` | | 403 | `authorization` | `PERMISSION_DENIED`, `FIELD_NOT_ACCESSIBLE`, `LICENSE_REQUIRED` | | 404 | `not_found` | `RECORD_NOT_FOUND`, `OBJECT_NOT_FOUND`, `ENDPOINT_NOT_FOUND` | -| 409 | `conflict` | `CONCURRENT_MODIFICATION`, `DUPLICATE_RECORD`, `DELETE_RESTRICTED` | +| 409 | `conflict` | `CONCURRENT_MODIFICATION`, `UNIQUE_VIOLATION`, `DELETE_RESTRICTED` | | 422 | `validation` | `MISSING_REQUIRED_FIELD` on an absent `controlled_by_parent` master reference (see [above](#missing_required_field)) — this row is an exception to the 400 row, not a second home for the code | | 429 | `rate_limit` | `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED` | | 500 | `server` | `INTERNAL_ERROR`, `DATABASE_ERROR` |