@@ -346,27 +346,27 @@ deriving it from this page.
346346- Check user has permission to see resource (row-level security)
347347- Resource may have been deleted
348348
349- #### ` DUPLICATE_RECORD `
349+ #### ` UNIQUE_VIOLATION `
350350** HTTP Status:** 409
351- ** Meaning:** Resource with unique constraint already exists
351+ ** Meaning:** The write collides with a unique constraint — a record already holds that value
352352
353353** Example:**
354354``` json
355355{
356- "success" : false ,
357- "error" : {
358- "code" : " DUPLICATE_RECORD" ,
359- "message" : " Account with email 'john@acme.com' already exists" ,
360- "details" : {
361- "resource" : " account" ,
362- "constraint" : " unique" ,
363- "field" : " email" ,
364- "value" : " john@acme.com"
365- }
366- }
356+ "error" : " A record with this email already exists" ,
357+ "code" : " UNIQUE_VIOLATION" ,
358+ "field" : " email" ,
359+ "object" : " account"
367360}
368361```
369362
363+ The engine throws ` DuplicateRecordError ` , whose in-process ` code ` is
364+ ` DUPLICATE_RECORD ` ; the REST door translates that envelope at the boundary, so
365+ every route answers the wire code ` UNIQUE_VIOLATION ` and the in-process spelling
366+ never crosses HTTP. The refusal is emitted as the flat body shown above, and
367+ ` field ` is best-effort — see
368+ [ HTTP API] ( /docs/protocol/kernel/http-protocol ) for the degraded shape.
369+
370370** How to fix:**
371371- Check for existing resource before creating
372372- Update existing resource instead of creating new one
@@ -947,7 +947,7 @@ Even error responses can be abused:
947947// Attacker tries to enumerate user emails
948948for (let i = 0 ; i < 1000000 ; i++ ) {
949949 await register ({ email: ` user${ i} @example.com` });
950- // Response: "DUPLICATE_RECORD " or "VALIDATION_ERROR"
950+ // Response: "UNIQUE_VIOLATION " or "VALIDATION_ERROR"
951951}
952952```
953953
0 commit comments