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.
Everything below elaborates this sentence.
The shape
An error the rail chose to raise has this body:
Code
| Field | What it is for |
|---|---|
error | The stable code. Switch on this, never on message. See the note below for the errors that carry no code. |
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 or header. Absent when the error is not about one field. |
fix_stage | Which stage of the workflow can resolve it. This is the routing hint for your UI. Absent where no stage was set. |
detail | Structured context to act on, present on some errors: the masked candidates behind IDENTIFIER_CONFLICT, the payer's verdict per line on VISIT_LINE_REFUSED, reasons[] on CLAIM_NOT_READY. Its shape is per code. |
details | A list of strings that repeats message with its underlying cause. For a log, not a screen. |
source | Which part of the service or which upstream the failure came from. |
trace_id | The trace for this request. Quote it in a support ticket. |
path, fix_stage, detail, details, source and trace_id are left out when empty, so
read them as optional. The body also declares a numeric code, but the service does not fill
it in; do not rely on it. The HTTP status is the status line.
fix_stage takes one of these values: authentication, identity, cover, selection,
eligibility, consent, visit, treat, preauth, close. The dictionary below lists the
one each code returns.
Not every error has a code. error holds a code only when the rail raised the error
deliberately.
- A missing or invalid token returns 401 with
error: "Unauthorized". - A refusal from authorization returns 403 with
error: "Forbidden". It comes from the OpenFGA permission check and names what failed inmessage: a missing tenant, a facility header a facility-type tenant did not send, or insufficient permissions. It is checked before the route runs, so a facility-type tenant that omits the facility headers meets this before it meetsFACILITY_CONTEXT_MISSING. - A body that cannot be read returns 400 with
error: "Bad Request"andmessage: "invalid request body: ...". - Some validation failures on the visit routes have no code;
errorthen holds the validation sentence, the same asmessage.
Switch on error for a code, and on the HTTP status for the rest. Do not treat a
non-UPPER_SNAKE_CASE error as a defect on these; it is the status text or the sentence.
Every response carries an X-Request-ID header. If the caller sends a W3C traceparent
header, the request joins that trace, so trace_id in the body matches your own logs.
Two rules follow from this shape.
Pre-flight returns every reason at once in reasons[], and VISIT_LINE_REFUSED returns every
refused line at once in detail, so you will not fix one and then discover a second behind it.
Request validation elsewhere reports the first failure only, so expect a second round there.
Codes are UPPER_SNAKE_CASE, always, wherever error holds a code. A lowercase code in
error is a defect. Report it rather than coding around it.
The dictionary
| Code | HTTP | What it means | What to do | fix_stage |
|---|---|---|---|---|
FACILITY_CONTEXT_MISSING | 400 | X-Facility-Id or X-Facility-Id-Type was absent or invalid on a v2 call. path names the header at fault. The type must be mfl, license-number, fr-code, registration-number or fid. | Set both facility headers in the HTTP client, not at individual call sites. | authentication |
MEMBER_NOT_FOUND | 404 | The identifier does not resolve to any registry record. | Confirm the identifier TYPE as well as the value. A missing or wrong type is the commonest cause of a false not-found. | identity |
IDENTIFIER_CONFLICT | 409 | The identifier matches more than one record and cannot be disambiguated automatically. detail carries the match count and masked candidates, with no unique_patient_id. | Collect a second identifier from the person at the desk. Do not retry the same call. | identity |
IDENTIFIER_TYPE_REQUIRED | 400 | The lookup body failed validation: identifier.value or identifier.type is empty. Only the first failure is reported, and this one code covers both. | Send the pair. A single free-text box is the commonest cause of a "patient not found" for a patient who is in the registry. | identity |
REGISTRY_TIMEOUT | 504 | The client registry did not answer inside the rail's budget. The lookup may still complete. The same code is returned when listing covers. A registry that answered with a refusal is REGISTRY_ERROR, not this. | Retry once with the same lookup_ref. Past your own wait budget, offer a pending state instead of holding the screen. | identity |
REGISTRY_ERROR | 502 | The client registry answered the lookup with a refusal, or could not be reached. A refusal carries the registry's own status and body in detail (upstream_status, upstream_body). | Re-check the identifier type and number first. If both are right, carry on as today, without the rail. | identity |
PAYER_SLADE_CODE_REQUIRED | 400 | A lookup by PAYER_MEMBER_NO arrived without identifier.payer_slade_code. A payer's member number is only unique within the insurer that issued it. | Fetch the payers list, let the desk pick the insurer, and send its code as identifier.payer_slade_code. | identity |
PAYER_SLADE_CODE_UNKNOWN | 400 | identifier.payer_slade_code is not a code in the payers list. | Send a code from GET /api/v2/payers, exactly as returned. | identity |
PAYERS_UNAVAILABLE | 502 | The payers list could not be read, which is not the same as there being no payers. It is 503 where the list is not configured on the deployment. | Retry. Do not show an empty payer picker. | — |
PII_CONSENT_REFUSED | 403 | The unmasked patient metadata read was refused: consent_ref is absent, unverified, expired, for another patient or not a pii_access consent. It is 503 where consent checking is not configured on the deployment. | Run a pii_access consent for this patient at stop 03 and send its reference as consent_ref. The masked lookup stays open without it. | identity |
NO_PAYER_REACHABLE | 502 | Every payer failed at once when listing covers, so the covers are unknown rather than absent. detail lists each source and why it failed. One payer failing alone is not an error: it appears in unavailable[] of a 200. | Retry before turning the patient away. Do not show it as "no cover". | cover |
COVER_ID_REQUIRED | 400 | The selection named no cover. | Send the cover_id exactly as the covers list returned it. | selection |
COVER_NOT_FOUND | 404 | No payer answered for that cover handle on this attempt. Most often the screen was left open and the member's cover changed since. | Fetch the covers list again and select from the current one. | selection |
COVER_NOT_SELECTABLE | 409 | The payer still knows the cover but will not honour it today — inactive, expired or outside its window. detail carries the payer's own status, reason and remediation. | Select another cover, or follow the remediation in detail. Read the payer's reason before telling the patient anything. | selection |
PATIENT_RECORD_CONFLICT | 409 | The unique patient id resolves to more than one patient in the clinical store, so a cover recorded now could attach to the wrong record. | The duplicates have to be merged first. Do not retry. | selection |
PATIENT_RECORD_NOT_FOUND | 404 | The person is known to the registry but has no clinical record yet, so there is nothing to record a cover against. | Register the patient in the clinical store, then select the cover again. | selection |
COVERAGE_WRITE_FAILED | 502 | The clinical store refused or failed the write, so the selection did not happen. It is 503 where selection is not available on the deployment. | Retry. Selecting the same cover twice is safe. | selection |
SELECTION_NOT_FOUND | 404 | No selection is known by that selection_ref. Another patient's selection is answered the same way. It is 400 when selection_ref is missing. | Select a cover first and use the selection_ref it returns. | eligibility |
SELECTION_LAPSED | 409 | The selection's cover has since stopped being usable — inactive or outside its window. | Fetch the covers list again and select a cover before walking benefits. | eligibility |
SELECTION_UNUSABLE | 409 | The selection cannot back this call. At benefits it names no payer. At consent it cannot decide which payer's rules apply (fix_stage consent; 503 if the deployment cannot resolve one). At visit open it is unknown, inactive or another patient's (fix_stage visit; 502 if it could not be read). | Select the cover again and use the new selection_ref. Retrying the same reference will not help. | eligibility |
BENEFIT_NOT_FOUND | 404 | parent_code names nothing in this member's benefit tree. | Walk down from the top of the tree again, using the codes the previous level returned. | eligibility |
BENEFITS_UNAVAILABLE | 502 | No benefits system could answer for this cover, so what it pays for is unknown rather than empty. It is 503 where benefits are not available on the deployment. | Retry before turning the patient away. An empty list would read as "nothing is covered"; this is not that. | eligibility |
FACILITY_NOT_IN_BENEFITS | 400 | The benefits system does not recognise the facility the call was made from. | Check X-Facility-Id and X-Facility-Id-Type. Retrying will not help. | eligibility |
PURPOSE_REQUIRED | 400 | The consent decision was asked for without a purpose. It is also the code for a body that fails validation for a reason no more specific code covers. | Send purpose: visit_start, visit_end, pii_access, eligibility_check or cover_view. Opening and closing a visit are separate purposes, not one reused. | consent |
PURPOSE_UNKNOWN | 400 | purpose is not one of the published values. It is refused rather than defaulted, because the purpose is also what the proof can later be redeemed for. | Use visit_start, visit_end, pii_access, eligibility_check or cover_view. | consent |
CALLBACK_URL_REQUIRED | 400 | The consent decision was asked for with nowhere to report its outcome. | Send callback_url on the call. | consent |
SUBJECT_NOT_FOUND | 404 | No member is known by that unique patient id, so there is nobody to take consent from. It is 400 when the id is blank in the request. | Look the patient up again and use the unique_patient_id it returned. | consent |
FACTS_UNAVAILABLE | 502 | The facts the decision reads could not be gathered, or the selected cover could not be read. No factor is offered rather than a guessed one. | Retry. Do not render a factor menu of your own in the meantime. | consent |
NO_MODALITY_AVAILABLE | 409 | No factor can prove consent for this member at this facility right now. | The message names the remedy — a delegate with authority consenting for the member, or a verified contact on the record. Show it. This is never a dead end to surface as a generic error. | consent |
AUTHN_SUBJECT_REQUIRED | 400 | An authentication was created with no subject. | Send the subject. It is the unique patient id the authentication is for. | — |
AUTHN_PURPOSE_REQUIRED | 400 | An authentication was created with no purpose. | Send purpose: visit_start, visit_end, pii_access, eligibility_check or cover_view. | — |
AUTHN_PURPOSE_UNKNOWN | 400 | The authentication's purpose is not one of the published values. | Use visit_start, visit_end, pii_access, eligibility_check or cover_view. | — |
AUTHN_CALLBACK_REQUIRED | 400 | An authentication was created with no callback_url to report its outcome to. | Send callback_url. It is refused at creation so an authentication is never minted that could not report. | — |
AUTHN_AGENT_REQUIRED | 400 | An authentication was created with no agent_id — the operator who asked for it. | Send the operator's national ID as agent_id. It is the audit record of who asked. | — |
AUTHN_NOT_FOUND | 404 | The tenant holds no authentication under that id. Another tenant's id is answered the same way. | Use the id the create call returned. | — |
PATIENT_NOT_FOUND | 404 | The clinical store holds no patient under that unique patient id, so there is nothing to open a visit against. It is 409 when the store holds more than one. | For a missing patient, register them, then open again. For duplicates, merge them first; opening now could attach to the wrong record. | visit |
CONSENT_REQUIRED | 400 | A service line names a cover, but the visit holds no consent and the line carried no consent_ref. | Run the consent ceremony at stop 03 and send its reference as consent_ref. | visit |
CONSENT_NOT_FOUND | 404 | No consent exists under that reference. It is 400 when consent_ref is not an authentication id at all, and the same code is returned by close (fix_stage close). | Send the authentication id the consent ceremony returned, unedited. | visit |
CONSENT_NOT_REDEEMABLE | 409 | The consent cannot be spent here. detail.refusal names why: AUTHN_NOT_VERIFIED, AUTHN_EXPIRED, AUTHN_CONSUMED, AUTHN_SUBJECT_MISMATCH or AUTHN_PURPOSE_REFUSED. It is 502 when the consent could not be checked. | For 409, run a new consent ceremony; an expired or consumed consent is not revived. For 502, retry. | visit |
COVER_NOT_IN_SELECTION | 409 | A service line names a cover the visit's selection does not. detail.cover names it. | A cover outside the selection needs its own selection and its own consent. | visit |
VISIT_ALREADY_OPEN | 409 | This patient already has a visit open on the rail. detail carries visit_ref and the endpoint to add a service line to it. It is 502 when the check itself could not be made: nothing was opened. | Add the service line to the open visit rather than opening a second one. On 502, retry. | visit |
MULTIPLE_VISITS_OPEN | 409 | The patient has more than one visit open, so the service cannot tell which a line belongs to. detail lists them all. | Close the visits that are finished, then retry. The choice is not made for you. | visit |
VISIT_LINE_REFUSED | 422 | The payer refused one or more service lines on open, so the visit was cancelled rather than left with a dead line. detail maps each refused line's code to the payer's verdict, all at once. | Address the verdicts in detail or drop the lines, then open again. | visit |
VISIT_NOT_FOUND | 404 | No visit exists under that visit_ref. | Use the visit_ref the open call returned. | visit |
VISIT_NOT_OPEN | 409 | The visit exists but no longer admits changes: it is closed or cancelled. Closing an already closed visit returns this too. | Read the visit before retrying. If your state is behind, refresh it; do not open a second visit to get past this. | visit |
VISIT_NOT_PAUSED | 409 | Resume was called on a visit that is not paused. | Read the visit's state. Nothing needs resuming. | visit |
VISIT_WRITE_FAILED | 502 | The clinical store could not be read or written, so nothing was recorded. This code also covers failed reads, such as listing visits. | Retry. Nothing half-happened. | visit |
SERVICE_LINE_MISSING | 409 | No service line exists under that reference on this visit. It is 404 on switch, retire and restore. | Add the service line first, or use the service_line_ref the visit returned. | visit |
SERVICE_LINE_NOT_SWITCHABLE | 422 | Switch was attempted on a line that is not authorized, so there is no authorization to switch. | Switch only an authorized line. Read the line's status first. | visit |
SERVICE_LINE_UNCHANGED | 422 | The new intervention is the same as the current one. | Send a different new_code, or leave the line alone. | visit |
SERVICE_LINE_NOT_RETIRABLE | 422 | Retire was attempted on a line that is not active, so there is nothing to retire. | Retire only an authorized line. Read the line's status first. | visit |
SERVICE_LINE_NOT_RETIRED | 422 | Restore was attempted on a line that is not retired. | Restore only a retired line. | visit |
LAST_SERVICE_LINE_FOR_COVER | 422 | Retiring this line would leave its cover with no active line. | Switch the line to another intervention, or cancel the visit. | visit |
SERVICE_LINE_ACTION_UNSUPPORTED | 422 | The payer behind this visit does not yet support switching or retiring a service line. | Cancel the visit and open it again with the right line. | visit |
SERVICE_LINE_ACTION_REFUSED | 422 | The payer refused the switch, retire or restore. detail carries the payer's verdict. | Read detail.refusal. The line is unchanged. | visit |
CLAIM_MISSING_FOR_COVER | 409 | The line, or the cover a document was addressed to, has no payer claim yet, so nothing can be switched, retired or attached to it. It is 502 on a Slade cover whose claim is not on the payer's system. | Add a covered service line so its claim is created, then retry. | visit |
CANCEL_REASON_INVALID | 400 | A cancel, switch or retire carried a reason that is not one of the known values. | Use one of the published reason values, or omit the reason. | visit |
PRACTITIONER_INVALID | 422 | The practitioner identification did not resolve in the Health Worker Registry under the given regulator. | Check the registration number and the regulation body. The wrong council is the likelier error. | visit |
PAYER_UNREACHABLE | 502 | The payer's system did not answer, so the record was not set there. The line is kept pending. It is 503 when the attachment proxy is not configured. At cover listing the same reason appears per source in unavailable[] of a 200, not as an error. | Retry the same request; it is safe. Do not surface this to the front desk as a patient problem. | visit |
SLADE_RAIL_UNWIRED | 502 | The cover bills through Slade EDI, which is not configured on this deployment. | Not fixable from the client. Contact support with the trace_id. | visit |
SLADE_PROVIDER_UNMAPPED | 502 | The facility could not be mapped to a provider on Slade, so the claim was not created there. | The facility must be onboarded on the payer. Contact support with the trace_id. | visit |
DIAGNOSIS_REQUIRED | 422 | The visit has no recorded diagnosis, and something downstream — a preauthorisation, a claim — needs one. | Record it while the clinician is still in the encounter. A diagnosis reconstructed at submission is a guess with a code on it. | clinical |
DIAGNOSIS_CODE_INVALID | 400 | The code is not in the ICD-11 release the payer is validating against, or it is not a codable entity — a chapter or grouping rather than a diagnosis. | Populate the picker from the terminology service and send only entries flagged codable. This is the commonest cause of a claim rejected days after the visit. | clinical |
DIAGNOSIS_NOT_LINKED | 422 | A diagnosis was recorded without the intervention it justifies, or a billed intervention has no diagnosis behind it. | Every diagnosis carries an intervention_code. The payer assesses the pair, not either half. | clinical |
DIAGNOSIS_INTERVENTION_MISMATCH | 422 | The intervention is not supported by the diagnosis it was linked to under the payer's clinical rules. | Read the detail block — it names both codes and the rule. Either the diagnosis is under-specified or the wrong intervention was chosen. | clinical |
PRIMARY_DIAGNOSIS_CONFLICT | 409 | More than one diagnosis on the visit is ranked primary. | Exactly one primary, any number of secondaries. Re-rank rather than deleting and re-recording. | clinical |
DIAGNOSIS_LOCKED | 409 | A diagnosis was edited or withdrawn after the claim was submitted. | The clinical record is amended, not rewritten. Raise the correction against the claim instead. | clinical |
CLINICAL_BUNDLE_INVALID | 400 | The FHIR bundle failed validation — an unknown resource type, a missing subject, or a coding without its system. | Read issues[]; each names the entry index and the element. A partially valid bundle is not partially written. | clinical |
CLINICAL_CONSENT_MISSING | 403 | The patient's proof does not extend to sharing the record beyond this facility, or to reading it back from elsewhere. | Clinical sharing is its own purpose at the consent stop. Payer consent does not carry it, and neither is retroactive. | consent |
EPISODE_CLOSED | 409 | A visit was attached to an episode of care that has already been closed out. | Open a new episode, or reopen the old one deliberately. A closed episode is a clinical statement, not a stale record. | clinical |
ORDER_PERFORMER_UNREACHABLE | 502 | The facility the order was routed to did not accept it inside the rail's budget. | The order is held and retried, not lost. Show it as routing rather than failed, and do not raise a second one. | clinical |
RESULT_ORDER_MISMATCH | 409 | A result was returned with observations the order did not ask for, or missing ones it did. | Return results against the expanded parameter list the order came back with, not against your own panel definition. | clinical |
PRESCRIPTION_NOT_ROUTED_HERE | 403 | A dispense was attempted against a prescription routed to a different pharmacy. | Dispense only from your own inbox. Where the patient walks in elsewhere, the prescription is re-routed rather than dispensed against. | clinical |
DISPENSE_EXCEEDS_PRESCRIBED | 409 | The quantity dispensed, added to what was dispensed before, is more than was prescribed. | Partial fills accumulate. Read what remains on the prescription rather than assuming the first dispense was the only one. | clinical |
SUBSTITUTION_NOT_ALLOWED | 409 | A different product was dispensed against a prescription the prescriber marked no-substitution. | The flag is set per item. Where the product is unavailable, route back to the prescriber instead of substituting. | clinical |
DOCUMENT_DATA_INCOMPLETE | 422 | A document was requested that cannot be generated — the clinical data it renders from is missing fields. | Read missing[]; each entry names the element. Record the clinical fact rather than writing the document by hand. | clinical |
REFERRAL_AUTH_EXPIRED | 409 | The authorisation taken at the source facility has passed its window before the patient arrived. | Re-authenticate at the receiving facility. The window is on the authorisation the referral carried — surface it to the patient when they are referred, not when they arrive. | consent |
REFERRAL_NOT_ACCEPTED | 409 | A visit was opened against a referral the receiving facility has not accepted. | Accept the referral first; acceptance is what opens the receiving visit against the source authorisation. | clinical |
DIAGNOSIS_REQUIRED | 400 | No primary diagnosis was supplied. | Exactly one primary diagnosis is required, coded — never free text. | preauth |
NARRATIVE_TOO_SHORT | 400 | The clinical narrative is below the enforced minimum length. | The narrative is read by a human reviewer. Treat the minimum as a floor, not a target. | preauth |
DOCTOR_NOT_LICENSED | 422 | The practitioner record has no active licence. | Practitioner validity is checked live at submission. Codify practitioners from the registry rather than storing names. | preauth |
MISSING_CLINICAL_FIELD | 422 | A field required by this benefit type on this payer is absent or malformed. | The message names the missing field. Requirements vary by benefit type and by payer. | preauth |
INVALID_STATE_TRANSITION | 409 | A message, cancellation or resubmission was attempted against a terminal reference. | Only a returned request may be resubmitted, and it keeps its original reference. | preauth |
BILL_LINE_CODE_INVALID | 400 | The bill line's code is not a known terminology code. | Populate the picker from the terminology service and send only codes it returns. | treat |
BILL_LINE_REFUSED | 400 | The payer would not accept the bill line. The payer's own verdict is the message — for example, no approved preauthorisation. | Read the message. It is the payer's reason, not the rail's, and the fix is usually on the service line or the preauth. | visit |
SERVICE_LINE_NOT_AUTHORIZED | 409 | The service line is not authorized, so there is no authorization for this charge to draw down. | Bill only against an authorized service line. Read its status: a pending or refused line cannot be charged. | visit |
PRACTITIONER_NOT_ON_VISIT | 409 | practitioner_ref names nobody on this visit, and the payer's claim names who performed the service. | Add the practitioner to the visit first, then charge. | visit |
PREAUTH_REQUIRED | 409 | The line's intervention needs preauthorisation and none is approved yet. | Submit the preauthorisation and wait for an approved or partial decision, then charge. | preauth |
PREAUTH_VERIFY_FAILED | 502 | The rail could not check whether the line's intervention needs preauthorisation. It does not guess in a direction that lets money leave. | Retry the charge. | preauth |
COVER_BALANCE_INSUFFICIENT | 409 | The cover cannot fund the line's full total. A tariff excess is not this error: that line bills and the member owes the copay. | Offer the operator an alternative or move the charge to another cover. Balances are live and shared across facilities. | treat |
SERVICE_LINE_USES_EXHAUSTED | 409 | The service line has no billing submissions left for the current period, whatever money remains. | Bill it another way or stop. Nothing on the cover changes this before the period rolls over. | treat |
INVOICE_NUMBER_REQUIRED | 400 | A charge on a Slade cover named no invoice number, so there is no claim to put it on. A SHA cover does not need one. | Send invoice_number on the bill item, or on each split that draws on a Slade cover. | treat |
INVOICE_NUMBER_IN_USE | 409 | The invoice number already names a claim on another cover of this visit. An invoice number belongs to one cover's claim. | Use a different invoice number for this cover's charges. | visit |
CLAIM_NOT_ON_VISIT | 409 | The claim_ref names no claim on this visit. Returned by pre-flight scoped to one claim, and by a claim attachment. | Read the visit's claims and use a claim_ref from that list. | visit |
PREFLIGHT_REQUIRED | 422 | Close was called with no passing claim pre-flight recorded for the visit. detail.run_preflight names the endpoint. The same code guards preauth submit, with fix_stage preauth. | Run close pre-flight, resolve every reason, then close. | close |
CLAIM_NOT_READY | 422 | The claim is incomplete: pre-flight still reports outstanding reasons. detail.reasons[] lists every one. The visit_end consent is not spent. | Resolve each reason in detail.reasons[], then close again. | close |
COPAY_OUTSTANDING | 402 | The patient's copay is uncollected. The message names the amount. | Collect the copay or record it as settled, then close. | visit |
COPAY_PROMPT_UNAVAILABLE | 501 | A copay prompt to the patient's phone was requested but prompting is not configured here. | Collect the copay another way and record it through the settle endpoint. | visit |
CLAIM_DISPATCH_FAILED | 502 | A cover's claim documents could not be composed and filed, or the claim could not be sent, so the claim did not go out. fix_stage is visit where the claim or encounter reference is not one the payer can act on. | Read the visit's claims before retrying close, so a claim already sent is not sent twice. If it persists, contact support with the trace_id. | close |
SLADE_ATTACHMENT_STORE_UNWIRED | 502 | This cover's documents are filed by reference to shared storage, which is not configured or could not be written. | Not fixable from the client. Contact support with the trace_id. | close |
SLADE_ATTACHMENT_REFUSED | 422 | The payer rejected the attachment. The payer's reason is in the message. | Replace the document with one the payer will accept, then attach again. | close |
UNKNOWN_EVENT | 400 | The callback registration contains an event name the rail does not publish. | Register only published event names. Unknown values are rejected outright. | authentication |
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.
| Call | What it does | What you keep |
|---|---|---|
GET /terminology/rail-errors.json | Flat list: code, http, stop, fix_stage, meaning, fix | codes[] |
GET /terminology/rail-errors.codesystem.json | CodeSystem, with http/stop/fix_stage as concept properties | concept[] |
GET /terminology/rail-errors.valueset.json | ValueSet, pre-expanded | expansion.contains[] |
The same set is loadable from the terminology service alongside every other value set the rail uses. See Value sets and code systems for how to query it there, and how to keep a local copy in sync.
How to build against errors
- 01Generate your error enum from the published value set at build time. A hand-maintained copy drifts the first week a code is added.
- 02Switch on `error`, never on `message`. Messages are written for humans, and their wording will change. For 401 and 403, switch on the HTTP status.
- 03Route 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.
- 04Show every reason at once where the API returns them together: pre-flight `reasons[]` and the `detail` of VISIT_LINE_REFUSED. Collapsing those down to a single first error puts back the queue the contract was written to remove.
- 05Treat 502 PAYER_UNREACHABLE, NO_PAYER_REACHABLE and BENEFITS_UNAVAILABLE as transient and retry. Never surface them to a front desk as though the patient has a problem.
- 06Log the `trace_id` with the code. It is what support needs to find the request.
- 07Log 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:
| Where it surfaces | What the desk sees | What actually went wrong |
|---|---|---|
| Identify | MEMBER_NOT_FOUND | Usually the identifier was sent without its type, or with the wrong one. Genuine absence is the rarer case. |
| Verify cover | NO_PAYER_REACHABLE | Every payer failed, so the covers are unknown rather than absent. Retry it. One payer failing alone is not an error: it appears in unavailable[] of a 200. |
| Verify cover | SELECTION_UNUSABLE | The selection cannot back the call, and retrying the same selection_ref will keep failing. Select the cover again. |
| Consent | NO_MODALITY_AVAILABLE | Nothing can prove consent for this member here right now. The message names the remedy; it is not a generic failure. |
| Visit | CONSENT_NOT_REDEEMABLE | The consent was spent, expired or for another purpose. detail.refusal says which. A new consent ceremony is the fix. |
| Visit | VISIT_NOT_OPEN | Nothing is broken; the visit has already closed or been cancelled. Refresh your state instead of opening a second visit. |
| Submit | CLAIM_NOT_READY | Pre-flight still has outstanding reasons. The consent was not spent, so fix each reason in detail.reasons[] and close again. |

