# Error dictionary

  An error tells you what happened, where in the request it happened, and which screen can
  fix it. If your UI shows only the first, the operator never sees the two parts they could
  have acted on.

## The shape

Every error the rail returns has the same body:

```json
{
  "error": "MISSING_DOCUMENTS",
  "message": "Itemised Invoice (final_bill) is still missing for TBC-4471-2026.",
  "path": "lines[].documents",
  "fix_stage": "billing"
}
```

| Field | What it is for |
|-------|----------------|
| `error` | The stable code. Switch on this, never on `message`. |
| `message` | Human-readable, written for the person at the desk. Display it, but do not parse it. |
| `path` | Where in the request the problem is, so you can highlight the offending field. |
| `fix_stage` | Which stage of the workflow can resolve it. This is the routing hint for your UI. |

Two rules follow from this shape.

Validation returns every reason at once, so you will not fix one error and then discover a
second behind it. A correction round stays one round instead of turning into a queue.

Codes are `UPPER_SNAKE_CASE`, always. A lowercase error code in a response is a defect.
Report it rather than coding around it.

## The dictionary

<ErrorDictionary />

## Retrieve it instead of transcribing it

The dictionary above is generated from one source, and that source is published as
terminology. Generate your enum from it rather than hand-copying a table that will drift.

<Wire
  rows={[
    {
      call: "GET /terminology/rail-errors.json",
      does: "Flat list: code, http, stop, fix_stage, meaning, fix",
      keep: "codes[]",
    },
    {
      call: "GET /terminology/rail-errors.codesystem.json",
      does: "CodeSystem, with http/stop/fix_stage as concept properties",
      keep: "concept[]",
    },
    {
      call: "GET /terminology/rail-errors.valueset.json",
      does: "ValueSet, pre-expanded",
      keep: "expansion.contains[]",
    },
  ]}
/>

<div className="rail-op-actions">
  <a className="rail-btn rail-btn--primary" href="/terminology/rail-errors.json" download>
    Download flat JSON
  </a>
  <a className="rail-btn" href="/terminology/rail-errors.codesystem.json" download>
    CodeSystem
  </a>
  <a className="rail-btn" href="/terminology/rail-errors.valueset.json" download>
    ValueSet
  </a>
</div>

The same set is loadable from the terminology service alongside every other value set the
rail uses. See [Value sets and code systems](/rail/reference/Value-Sets) for how to query it
there, and how to keep a local copy in sync.

## How to build against errors

<BuildSequence
  steps={[
    "Generate your error enum from the published value set at build time. A hand-maintained copy drifts the first week a code is added.",
    "Switch on `error`, never on `message`. Messages are written for humans, and their wording will change.",
    "Route on `fix_stage`. Send the operator to the screen that can resolve the problem instead of showing a message and leaving them to hunt for it.",
    "Show every reason at once. The API returns them together, and collapsing them down to a single first error puts back the queue the contract was written to remove.",
    "Treat 502 PAYER_UNREACHABLE as transient and retry. Never surface it to a front desk as though the patient has a problem.",
    "Log the code rather than the prose. When a flow stalls in production, the sequence of codes is what you diagnose it from.",
  ]}
/>

## The ones that are not what they look like

A short list of codes that reliably get misread:

<CostTable
  rows={[
    {
      where: "Identify",
      symptom: "MEMBER_NOT_FOUND",
      cause: "Usually the identifier was sent without its type, or with the wrong one. Genuine absence is the rarer case.",
    },
    {
      where: "Eligibility",
      symptom: "PAYER_UNREACHABLE",
      cause: "Transient, so retry it. The answer is about infrastructure and says nothing about eligibility.",
    },
    {
      where: "Consent",
      symptom: "CONSENT_SESSION_PENDING",
      cause: "The session is stale rather than busy. Reject it and create a new one, because retrying create will keep failing.",
    },
    {
      where: "Submit",
      symptom: "NEEDS_REVIEW",
      cause: "This is a warning rather than a blocker. Billing carries on, but nothing reaches submission until someone confirms it.",
    },
    {
      where: "Submit",
      symptom: "ALREADY_CLOSED",
      cause: "Nothing is broken; your state is simply behind. Treat it as success rather than opening a second visit.",
    },
  ]}
/>
