The next_action contract
The client reads its next move out of the response instead of guessing it.
Everything below elaborates this sentence.
What it is
Every response from the rail carries a next_action object. Its type names the move; where
a call is required, endpoint carries the fully formed URL to make it against.
Code
Take the endpoint from the response. Do not construct it. What comes back already has the
identifiers interpolated, so you get /api/v2/patients/SIL-UPI-0091774/covers rather than a
template you have to fill.
Why it exists
There are two reasons, and the second is the one that bites in production.
The first is that it keeps payer branching out of your client. Payers differ on whether a
preauthorisation is needed, on whether a reservation must be created explicitly or is made
for you, and on whether doctor sign-off is routed elsewhere first. Those differences reach
you as different next_action values, so you do not keep a code path per payer.
The second is that it handles legitimate skips. Hard-coding the sequence works until a step
is correctly skipped: an authorisation already granted and still valid, a reservation
auto-created on approval, a preauth that turned out not to be required. A client that walks
a fixed list either repeats work or stalls. Reading next_action avoids both, and it takes
no conditionals on your side.
The full enum
| Call | What it does | What you keep |
|---|---|---|
select_cover | Render the cover chooser and call cover selection | endpoint |
check_eligibility | A cover was selected, so check eligibility against it | endpoint |
select_benefit | Show the benefit or item picker, fresh or because the previous choice is now blocked | endpoint |
assign_location | A service location must be set before anything else proceeds | — |
authenticate | Offer the consent factors listed in available_factors | available_factors |
submit_challenge | A one-time code was dispatched, so collect and submit it | endpoint, expires_at |
submit_preauth | Authorisation granted and an item needs preauthorisation | endpoint |
manual_confirmation | Preauth is required but confirmed outside the API, so proceed provisionally | — |
await_decision | Poll the endpoint, or wait for the registered callback | endpoint, poll_after_ms |
reserve | An approval needs an explicit reservation call | endpoint |
fix | Pre-flight found blockers, and reasons[] names each one with a fix_stage | reasons[] |
submit | Every gate has passed, so close the claim | endpoint |
none | Nothing further is required right now | — |
How to build against it
- 01Model next_action.type as an enum in your own code, with an explicit unknown case. New values can be added; an unknown value should surface as 'unhandled step' rather than crashing or silently stopping.
- 02Write one dispatcher that switches on the type. Every screen in your flow is reached from it. Resist adding a second path 'just for this case'.
- 03Always take endpoint from the response. If your code contains a string-concatenated rail URL, it will break the next time a payer changes something.
- 04Treat await_decision as a state, not a wait. Persist it, honour poll_after_ms, and let the callback settle it if one arrives first.
- 05Treat none as terminal for the current step, not for the flow. It means nothing more is needed here, and your own workflow decides what happens next.
- 06Log the type on every transition. When a flow stalls in production, the sequence of next_action values tells you where it stopped.
The common mix-up
none and await_decision look similar from a UI perspective, because in both cases nothing
is required of the operator right now. They are not the same.
none means the rail has nothing further for you at this point. await_decision means a
decision is outstanding somewhere else and will arrive, and that you are expected to be
listening. Treat the second as the first and a preauth approval can sit unnoticed for a
day.
Errors carry their own routing
Failures use the same discipline. Every error carries error, message, path and
fix_stage. Validation returns every reason at once rather than one at a time, so a
correction takes one round rather than a queue of them.
fix_stage tells you where in the sequence the correction belongs. Combined with
next_action.type: "fix" and the reasons[] array at pre-flight, you can route an operator
straight to the screen that can resolve the problem instead of asking them to hunt.
Next: see it drive a real sequence on the golden path.

