Skip to main content
Every error returns the same shape, so you can handle them uniformly:
{
  "error": {
    "code": "validation_error",
    "message": "amount must be a positive integer",
    "details": [{ "path": "amount", "issue": "expected positive integer" }]
  }
}
  • 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

StatuscodeMeans
400bad_requestMalformed request the validator couldn’t accept.
401unauthorizedMissing or invalid credential.
403forbiddenAuthenticated, but lacks the permission (or the resource isn’t yours).
404not_foundNo such resource in this tenant + mode.
409conflictA uniqueness or state-machine violation (e.g. duplicate slug, illegal transition).
422validation_errorThe body failed schema validation. See details.
429rate_limitedToo many requests. Back off.
500internal_errorOur 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. A 422 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

A 500 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.