9.2 Structured errors (application/problem+json)

Every failure in RS2 — from any service, prebuilt or custom — is returned as application/problem+json with a machine-readable code. There are no bare string errors. This uniformity means a client can handle failures programmatically across the whole surface.

The shape

{ "type": "https://rs2.dev/errors#validation_failed",
  "title": "Unprocessable Entity",
  "status": 422,
  "code": "validation_failed",
  "detail": "…",
  "tenant": "acme",
  "traceId": "…",
  "retryable": false,
  "errors": [ { "path": "/total", "error": "…" } ] }
  • code — the stable, machine-readable identifier; branch on this, not on title or status text.
  • retryable — whether retrying might succeed.
  • traceId — correlates to the log line for this request (9.6).
  • errors — present for validation failures: an array of {path, error}.

The codes

code status notes
bad_request 400
path_unsafe 400 rejected by the router for every service (3.2)
unauthorized 401 missing/invalid credentials; lockout adds Retry-After
forbidden 403 principal lacks a required role
capability_denied 403 sandboxed code used an ungranted capability; extra capability field
not_found 404
conflict 409 config version mismatch (If-Match), in-flight idempotent duplicate
payload_too_large 413
validation_failed 422 extra errors array
idempotency_key_reuse 422 same key, different payload
internal 500
engine_unavailable 501 build lacks the engine for this code type
contract_violation 502 custom service broke the contract (bad output, crash, compile failure)
limit_exceeded 503 retryable; extras limit, observed, cap, retryAfterMs

Pipeline failures carry step detail

A failing pipeline merges a pipeline object into the body (7.9):

"pipeline": { "failedStep": "/2",
              "steps": [ { "step": "/0", "kind": "call", "status": 200 }, … ] }

So you can see which step failed and how far execution got, without re-running.

Handling errors well

  • Switch on code, not on status text — codes are stable.
  • Respect retryable and Retry-After/retryAfterMs — don't hammer a limit_exceeded.
  • Log the traceId when reporting a problem; it's the key to the server-side log line (9.6).
  • For validation_failed, surface the errors[].path to the user — they're JSON Pointers into the offending document.

Next: idempotency, which lets you safely retry the requests that are retryable.


Previous: 9.1 The discovery surface · Manual home · Next: 9.3 Idempotency keys and replay →