code— a stable machine-readable string. Switch on this.message— a human-readable summary. Log it; don’t parse it.details— present on validation errors, one entry per failed field.
Status codes
| Status | code | Means |
|---|---|---|
400 | bad_request | Malformed request the validator couldn’t accept. |
401 | unauthorized | Missing or invalid credential. |
403 | forbidden | Authenticated, but lacks the permission (or the resource isn’t yours). |
404 | not_found | No such resource in this tenant + mode. |
409 | conflict | A uniqueness or state-machine violation (e.g. duplicate slug, illegal transition). |
422 | validation_error | The body failed schema validation. See details. |
429 | rate_limited | Too many requests. Back off. |
500 | internal_error | Our fault. Safe to retry idempotently. |
Validation is strict and upfront
Every endpoint validates its body against a schema before any work happens — so a bad request never half-applies. A422 with details tells you exactly which field to fix. Required fields, types, enums, lengths, and cross-field rules (e.g. “percentage discount ≤ 100”) are all enforced at the edge.
Retrying safely
A500 or 429 is safe to retry if you send an Idempotency-Key — Duro will replay the original result rather than double-apply. Never blind-retry a mutation without one.