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
Every error the rail returns has the same body:
Code
| 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
| Code | HTTP | What it means | What to do | fix_stage |
|---|---|---|---|---|
UNAUTHENTICATED | 401 | No token, an expired token, or a token the rail does not recognise. | Refresh proactively on a timer rather than reactively on a 401. | authentication |
FACILITY_CONTEXT_MISSING | 400 | The facility headers were absent, so no facility-scoped rule could be resolved. | Set the facility headers in the HTTP client, not at individual call sites. | authentication |
OUTSIDE_FACILITY_GEOFENCE | 403 | The call originated outside the configured radius of the facility. Checked before any stop-specific gate. | Read distance_m, allowed_radius_m and facility_code from the detail block. Test environments often use a different radius. | 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. | Collect a second identifier from the person at the desk. Do not retry the same call. | identity |
IDENTIFIER_TYPE_REQUIRED | 400 | An identifier value arrived without its type. A number on its own resolves to nothing. | 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 |
IDENTIFIER_TYPE_UNKNOWN | 400 | The identifier type is not in the current value set — usually a hard-coded list that has since moved, or a legacy numeric requestIdType sent in place of the string code. | Populate the type picker from GET /api/v2/clinical/concepts?system=IDENTIFIER-TYPES and send the string code. | identity |
REGULATOR_REQUIRED | 400 | A practitioner was searched by registration number with no regulator. The same digits can be valid under more than one council. | Send the regulator alongside the number — KMPDC, COC or NCK. Only a PUID lookup may omit it. | identity |
PRACTITIONER_NOT_FOUND | 404 | No practitioner matches that regulator and registration number. | Re-check the regulator before re-typing the number; the wrong council is the likelier error. | identity |
MEMBER_DECEASED | 409 | The record resolved, but the registry reports the person as deceased (is_alive: false). | Stop and escalate clinically. Do not open an encounter or bill against the record. | identity |
REGISTRY_TIMEOUT | 504 | The client registry did not answer inside the rail's budget. The lookup may still complete. | Retry once with the same lookup_ref so the attempt is de-duplicated rather than recording a second consent. Past your own wait budget, offer a pending state instead of holding the screen. | identity |
COVER_INVALID | 404 | The cover does not resolve, or its status is not active. | Re-list covers and let the operator choose again. | cover |
COVER_EXPIRED | 409 | The cover's policy window does not include today. | Select a different cover, or collect payment another way. | cover |
SCHEME_CODE_REQUIRED | 400 | The cover is live on more than one scheme and no scheme was named in the selection. | Render schemes as covers so the operator makes one selection rather than two. | cover |
ELIGIBILITY_REF_NOT_FOUND | 404 | The eligibility reference is unknown or has expired. | Re-run eligibility. The reference is visit-scoped and short-lived by design. | eligibility |
PAYER_UNREACHABLE | 502 | The payer did not respond within the configured timeout. | Retry — it is safe and idempotent on the selection reference. Do not surface this to the front desk as a patient problem. | eligibility |
CONSENT_REQUIRED | 403 | An operation that reads or acts on a member's record was attempted without a recorded consent. | Open a consent session at stop 03 and carry the authorization_ref. Resolving an identity does not need consent; reading a record does. | consent |
CALLBACK_URL_REQUIRED | 400 | A consent session was opened with nowhere to deliver its outcome. | Send callback_url on the open call. If your system cannot receive inbound calls, send delivery: "poll_only" and poll the endpoint the response gives you. | consent |
METHODS_NOT_CLIENT_DECLARED | 400 | The open call tried to specify which consent factors to use. | Remove the field. Which factors are legal is resolved from the member, facility, payer and benefit — it is never a client choice. | consent |
CONSENT_PURPOSE_INVALID | 400 | purpose is not one of the published values. | Use visit_open, visit_close, share_record or pii_access. Opening and closing a visit are separate purposes, not one reused. | consent |
EMBED_ORIGIN_NOT_REGISTERED | 403 | The page attempting to frame the consent UI is served from an origin the rail does not know. | Register every origin that will embed the UI, including staging hosts. The browser will report this as a frame-ancestors refusal with no request reaching you. | consent |
SUBJECT_NOT_CONSENTABLE | 403 | No consent can be taken for this person — most often a deceased record. | This is a data condition, not a transient failure. Do not retry; resolve the record. | consent |
NO_FACTOR_AVAILABLE | 422 | Nothing can legally prove consent for this member, at this facility, right now. | The response names the remedy — a whitelist request, adding a verified number, or enrolling prints. Show it. This is never a dead end you should surface as a generic error. | consent |
CONSENT_SESSION_PENDING | 409 | An earlier consent session is still open, which blocks creating a new one. | Reject the stale session explicitly, then create a new one. Retrying create will keep failing. | consent |
CONSENT_SESSION_EXPIRED | 410 | The session outlived its window before it was completed. | Open a new session. Expiry is a normal path — a front desk gets interrupted. | consent |
CHALLENGE_REJECTED | 422 | A wrong or expired code, or attempts exhausted. | Attempts are limited per authorisation; beyond that, a new authorisation object is required. | consent |
PAYER_METHOD_BLOCKED | 403 | The requested factor is disabled for this payer and was never offered. | Only offer factors the rail returned. Do not render a factor menu of your own. | consent |
HARDWARE_MISMATCH | 422 | The capture device does not match this workstation's registration. | Register the device against the workstation before capture. | consent |
NO_LOCATION_ASSIGNED | 409 | A visit was opened before a service location was set. | Set the service location at the front desk. It is a workflow step, not a field you can invent. | visit |
BENEFIT_EXHAUSTED | 409 | The item's balance or counters were consumed — possibly elsewhere, between eligibility and now. | Re-check items and offer the operator an alternative. Balances are live and shared across facilities. | benefit |
WAITING_PERIOD | 409 | The benefit's waiting window has not yet elapsed. | Retry after the date given. Nothing can be done before it. | benefit |
VISIT_ALREADY_OPEN | 409 | Another active visit of the same class exists for this member. | Close the earlier visit first. Concurrent visits of one class are not permitted. | 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 |
UNMAPPED_CODE | 422 | The billing code does not resolve to a live item with a path to a payer revenue code. | Declare the price-list item in mapping first. Anything undeclared stays off the claim entirely. | mapping |
INVALID_CODE_COMBINATION | 422 | The line conflicts with another active line on the visit. | The message names both conflicting codes. Order matters — ask before adding where your workflow allows. | billing |
LINE_NOT_RETIRABLE | 409 | Retirement was attempted on a per-diem line outside a level change. | Move per-diem lines through a level change. Editing is not a supported operation. | billing |
RESERVATION_EXCEEDED | 409 | The amount requested exceeds what is available at the moment of the call. | Re-read the item's balance. It may have been consumed elsewhere since eligibility. | billing |
ALREADY_RESERVED | 409 | A reservation already exists for this code on this visit. | Release the existing reservation with a reason and create a fresh one. Nothing is overwritten silently. | billing |
PHARMACY_NOT_PAYABLE | 403 | The product is not reimbursable or is not registered. | It may still be sold as cash. This is a payability answer, not a dispensing one. | billing |
NOT_READY | 422 | Close was called while pre-flight still reports blockers. | Run pre-flight and resolve every blocker-severity reason first. | submission |
MISSING_DOCUMENTS | 422 | A document required by a billed item has not been attached. | The required document list was returned with the item at eligibility. Attach as captured, not at the end. | billing |
NEEDS_REVIEW | 200 | Warning, not a blocker — an override was applied earlier without confirmation. | It does not stop billing, but it cannot ride through to submission unexamined. Someone must confirm it. | authorization |
CALLBACK_NOT_REGISTERED | 409 | No callback registration exists for claim decisions. | Register callbacks at onboarding, not when you first need them. | authentication |
CLAIM_DATE_OUT_OF_COVER | 409 | A billed date falls outside the selected cover's validity window. | Check the service dates against the cover chosen at the start of the visit. | cover |
RESERVATION_MISSING | 409 | A line has no active reservation at discharge time. | Return to billing. Every unretired line must trace to a reservation. | billing |
ALREADY_CLOSED | 409 | Discharge was attempted on a visit that is already closed. | Treat as success if your own state is behind; do not open a second visit. | submission |
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.
- 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. The API returns them together, and collapsing them down to a single first error puts back the queue the contract was written to remove.
- 05Treat 502 PAYER_UNREACHABLE as transient and retry. Never surface it to a front desk as though the patient has a problem.
- 06Log 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. |
| Eligibility | PAYER_UNREACHABLE | Transient, so retry it. The answer is about infrastructure and says nothing about eligibility. |
| Consent | CONSENT_SESSION_PENDING | The session is stale rather than busy. Reject it and create a new one, because retrying create will keep failing. |
| Submit | NEEDS_REVIEW | This is a warning rather than a blocker. Billing carries on, but nothing reaches submission until someone confirms it. |
| Submit | ALREADY_CLOSED | Nothing is broken; your state is simply behind. Treat it as success rather than opening a second visit. |

