Errors

Every /v1 error response carries the same JSON shape so you can switch on code in client code instead of parsing message strings. The anonymous external-register endpoint keeps its legacy NestJS body shape — see the external-register guide for that one specifically.

{
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "details": [
    { "path": ["data", "@participant.email"], "message": "Expected a valid email" }
  ]
}

Status codes you should handle

StatusWhen
400 Body, query, or path parameter failed Zod validation. Fix and retry; the response details array lists each issue with a JSON path. Bodies sent as application/x-www-form-urlencoded are rejected — the API only accepts JSON.
401 Missing, malformed, revoked, or expired bearer token. Re-check the Authorization header format (Bearer jdo_…) and verify the token hasn't been revoked from the admin UI.
403 Token is valid but the request named a clientCode or eventCode outside the account's allowed scope. This is configuration, not a coding error — contact your administrator if you need wider access.
404 The entity doesn't exist, was soft-deleted, or sits outside the token's scope (we don't distinguish — that would leak existence). A 404 on a known-good id usually means scope.
409 Optimistic concurrency conflict — another write landed between your read and write. Re-fetch the entity and retry; do not assume your client-side state is correct. Only returned for aggregates with a revision counter.
429 Rate-limited. The anonymous external-register endpoint allows 20 requests/minute per source IP. Authenticated /v1 endpoints enforce a per-account quota (default 60 req/min, raise on request). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds); 429 responses additionally carry Retry-After.
5xx Server-side problem. Safe to retry with exponential backoff; the request was almost certainly not partially applied (writes are transactional). Persistent 5xx → contact support with your token's prefix and a recent x-request-id header if you've captured one.

Retrying safely

  • GETs are idempotent — retry freely with exponential backoff (e.g. 1s, 2s, 4s, capped at 30s).
  • POSTs / PATCHes on financial documents accept an idempotencyKey; pass the same key on retry to dedupe.
  • 4xx errors (validation, auth, scope) are NOT retryable — fix the request first.
  • 429 retry-after delays follow the Retry-After header when present.

Validation error details

For 400 responses driven by Zod, the details array carries one entry per offending field. Each entry's path is the JSON path into the request body (or query params for list endpoints) and message is the human-readable issue. Display them per-field to users rather than showing a single generic error.