# 06 · Preauthorize

  Approval is per item and can be partial. Bill against what was approved, never against
  what you asked for.

## Why this stop exists

Some items may not be done until someone has agreed to pay for them. Two things put a line
here: an item flagged as requiring preauthorisation at the benefit level, or an item flagged
conditional whose condition has now been met.

Either way the affected lines are **held**: they sit visibly on the bill while everything
else proceeds normally. Approval releases them without touching the rest of the invoice.

This is an interchange. The rail forks here between carrying on with the billing and waiting
for a decision that is being made somewhere else.

## One envelope, one clinical block

Every preauthorisation carries the same outer envelope: the visit, the requesting doctor,
the primary diagnosis, the billed items. That part never changes.

What changes is the **clinical block**, and the benefit type decides which one applies. A
surgical request does not have to prove the same things as a renal, oncology, radiology or
optical one.

<Anatomy
  cards={[
    {
      title: "Envelope (always)",
      required: ["visit_id", "request_type", "narrative", "service_start", "items[]", "diagnosis", "doctor"],
      note: "request_type is planned or emergency. narrative has an enforced minimum length, because a human reads it.",
    },
    {
      title: "Surgical",
      required: ["procedure", "admission_type", "surgeon_notes"],
      optional: ["pre_op_reports", "second_opinion"],
      note: "Surgery date does not default. A major procedure may require several sign-offs before a decision is possible.",
    },
    {
      title: "Renal",
      required: ["modality", "sessions_per_week", "dry_weight"],
      optional: ["latest_creatinine"],
    },
    {
      title: "Optical",
      required: ["visit_date", "lens_type", "prescription"],
      optional: ["frame_details", "previous_specs", "last_replacement"],
    },
  ]}
/>

The division of labour is deliberate. Your system already knows what clinical data belongs
to each benefit type, and populates it. The rail then confirms, before it accepts anything,
that everything the benefit type requires *on the current payer* is present and well formed.
Where something is missing it rejects the request and names the missing field, rather than
passing a half-formed request downstream.

## Submit

<ApiRef method="POST" path="/api/v1/preauths" to="/rail-api/06-preauthorize">
  Submit a preauthorisation. The rail validates it against the requirement map for this benefit type and payer.
</ApiRef>

Submission accepts an `idempotency_key`. A retry after a network failure then returns the
original reference rather than creating a duplicate. Use it, because duplicates here are
expensive to unpick.

## The states

There is one status enum whichever payer is deciding, so your state machine never has to
branch on payer.

<Wire
  rows={[
    { call: "submitted", does: "Received, routing in progress", keep: "not terminal" },
    { call: "under_review", does: "A reviewer or rule is assessing it", keep: "not terminal" },
    { call: "awaiting_doctor_review", does: "Routed for clinical sign-off", keep: "doctors_signed_off / doctors_required" },
    { call: "awaiting_manual_approval", does: "A payer officer must approve by hand", keep: "not terminal" },
    { call: "approved", does: "Full approval", keep: "authorization_code" },
    { call: "partially_approved", does: "Some items approved, some not", keep: "per-item amounts" },
    { call: "rejected", does: "No items approved", keep: "terminal" },
    { call: "returned", does: "Sent back for correction", keep: "resubmit under the SAME reference" },
    { call: "expired", does: "The decision window elapsed", keep: "terminal" },
    { call: "withdrawn", does: "An approval already given is taken back. It is rare and terminal, and most state machines have no branch for it.", keep: "terminal, stop billing against it" },
    { call: "cancelled", does: "Withdrawn by the requester before a decision", keep: "terminal" },
    { call: "lapsed", does: "Approved, but the window to start the work passed unused", keep: "terminal, request again" },
  ]}
/>

Twelve states, and the three at the bottom are the ones integrations miss. An approval can be
withdrawn after it was given, so "approved" is not a fact you can cache and stop watching. Keep
listening on the reference until the work is billed.

  Approval and the window to use it are two different things. An elective approval that is never
  acted on lapses, and the exact window is one of the figures still being confirmed, see
  [time limits](/rail/reference/Time-Limits). Store the expiry that comes back with the approval and
  show it on the case rather than inferring it.

## The decision

<ApiRef method="GET" path="/api/v1/preauths?preauth_reference=" to="/rail-api/06-preauthorize">
  The current state, and the decision once there is one. The body is identical to the callback's.
</ApiRef>

Bill checks `valid_from` and `valid_to` again when the line is added, so the check does not
only happen here. Billing after the window closes needs a fresh preauthorisation rather than
a reuse of this decision.

  On some payers an approval creates the reservation by itself, and `next_action` points
  straight past it to billing. On others you have to request the reservation explicitly, and
  `next_action` says `reserve`. Read it rather than assuming. This is exactly the case a
  hard-coded sequence gets wrong.

## Seven request types, and they behave differently

A preauthorisation is not one shape. The type decides the required fields, the validation, and in
some cases whether a doctor has to sign it, so read the type from the procedure rather than
defaulting to a generic request.

<Wire
  rows={[
    { call: "OUTPATIENT · INPATIENT", does: "The general paths. Neither demands a type-specific clinical block, though inpatient carries injury and admission detail where relevant.", keep: "the type" },
    { call: "SURGICAL", does: "Its own clinical block, an anaesthesia type, and, where enabled, a doctor's authorisation before it can proceed. It needs a credentialed surgeon, and where an anaesthetist is required, that is a second named practitioner.", keep: "surgery date" },
    { call: "RENAL", does: "The strictest of the set: sessions required and an expected session date are both mandatory, because dialysis is authorised as a course rather than an event.", keep: "sessions_required, session date" },
    { call: "ONCOLOGY", does: "Sessions and expected date, plus comorbidity and metastases detail. Live for named facilities rather than everywhere.", keep: "sessions_required" },
    { call: "RADIOLOGY", does: "Clinical indications and, where sedation is involved, an anaesthesia type.", keep: "clinical indications" },
    { call: "OPTICAL", does: "A prescription block per eye (sphere, cylinder, axis, add) plus frame detail. Age limits apply to some optical benefits.", keep: "the prescription" },
  ]}
/>

  The set above is what the rail models. Which of them a given payer accepts is configured per payer,
  so a type that works on the national scheme may not be enabled on a private insurer. Read the flags
  on the procedure, `needs_preauth` and the speciality-specific ones for renal, oncology, radiology,
  surgical and optical, rather than inferring the type from the benefit's name.

**Sessions are the pattern worth reading.** Renal and oncology authorise *a course of treatment*
rather than a single act: you say how many sessions and when they start, and the approval covers the
series. It is the same idea as `isMultisession` on the procedure, so do not raise a fresh request
per session.

## Required fields differ per request type

Each clinical path has its own required set, and the differences are exactly where requests get
rejected. Four of them are worth stating plainly, because earlier material had them wrong:

<Wire
  rows={[
    { call: "Surgical", does: "Seven required fields, not six. The surgery date is required, though earlier material described it as optional.", keep: "surgery_date" },
    { call: "Renal", does: "Five required fields, not four. The start date is required.", keep: "start_date" },
    { call: "Oncology", does: "Five required fields, not four. There is no cancer-staging field on this path, whatever earlier material implied.", keep: "—" },
    { call: "Optical", does: "All four description fields can be filled. It is the lens prescription that cannot.", keep: "—" },
  ]}
/>

  A practitioner's regulating body is sent as **`regulation_body`**. Sending `regulator` does not
  degrade gracefully: the request is rejected. The concept is the one taught at
  [01 · Identify](/rail/stops/Identify), and only the wire name differs, so it is worth grepping your
  code for.

**Extra items are dropped quietly rather than refused.** If you send items the request type does
not accept, the preauthorisation is created without them and nothing tells you. Compare what you
sent with what comes back, item by item, rather than trusting a 201.

## Decisions arrive; they are not returned

Preauthorisation is asynchronous. There is no synchronous approval.

- Register a callback once for `preauth.decision`. It is signed, retried with backoff, and safe to receive twice.
- Poll `GET` at the interval in `poll_after_ms`. It returns the same body as the callback.

Build both. The callback is the fast path; polling is the guarantee.

  It is easy to assume both run through [03 · Consent](/rail/stops/Consent), and they do not. That
  stop authenticates **the member**: is this the person, are they present, do they permit this. A
  doctor's approval is a practitioner's clinical decision on a specific request, recorded against
  the preauthorisation with its own review status, its own doctor roles (a surgeon, and where
  required an anaesthetist), and its own channels.

  They share a shape: a request goes out, an answer arrives out of band, and neither is something
  you poll into existence. Their purpose is different, so do not try to satisfy a doctor's approval
  with a member's proof, or the reverse.

## The doctor workflow

Where an intervention needs clinical sign-off, the request has to reach a specific practitioner.
This is the most under-documented part of the step relative to how often it goes wrong.

<Wire
  rows={[
    { call: "Channels differ per facility", does: "A request can go to the doctor's app, by two-way SMS, or by email, and not every facility is enabled for all three. A request sent on a channel the facility is not enabled for never arrives at all.", keep: "the enabled channels" },
    { call: "Resend is required behaviour", does: "If the first request fails, or the doctor never saw it, you must be able to resend to any doctor on the preauthorisation.", keep: "—" },
    { call: "Remove is required too", does: "After a rejection, the doctor can be removed and another attached.", keep: "—" },
    { call: "Some interventions need several doctors", does: "More than one signature triggers peer review. Read the requirement from the intervention rather than assuming one doctor is enough.", keep: "how many are required" },
  ]}
/>

A peer review decision is recorded, displayed, and **final once made.** Build the display, because
there is no path that reverses one.

  A surgical request needs a practitioner registered and credentialed in the surgeon speciality
  rather than one who merely holds a valid licence. That is a speciality check at
  [01 · Identify](/rail/stops/Identify), done when the clinician is chosen, hours before this request
  is raised.

## Documents, and when each is checked

The required list is published per intervention, and **the check happens at different points**:
some documents at preauthorisation, some at the claim. Inpatient submissions need a discharge
summary and a final bill, while maternity needs only the discharge summary. Collecting the right
set at the wrong moment still fails.

<BuildSequence
  steps={[
    { do: "Read the list from the intervention", detail: "Not from a general list. Two interventions in the same category can require different documents." },
    { do: "Note which stage each belongs to", detail: "Preauthorisation documents and claim documents are separate sets, and the response distinguishes them." },
    { do: "Make uploads idempotent", detail: "A retried upload must not create a second attachment." },
    { do: "Send the correct content type", detail: "A PDF sent as something else produces a confusing failure rather than a clear rejection." },
  ]}
/>

  Whether attachment checking is enforced in production is not yet confirmed. If it is off today,
  an integration with missing documents will pass quietly and start failing the day it is switched
  on. Build to the published requirements rather than to what the environment currently accepts.

## Every line carries its scheme code

A preauthorisation item, like a claim line, is billed against a fund, and the **scheme code on the
line is what routes it there.** It is not an optional override. Lines on one visit can legitimately
carry different scheme codes, because a member's covers differ by benefit.

Send it on every item. A line without one, or with the wrong one, is routed to the wrong fund and
comes back as an assessment problem rather than a routing problem.

## What breaks here

<CostTable
  rows={[
    {
      where: "Preauth",
      symptom: "\"We sent three items and only one came back\"",
      cause: "Some payers accept one item per request. Send one request per item unless you have confirmed otherwise, because the quiet drop is the worst failure mode on this stop.",
    },
    {
      where: "Preauth",
      symptom: "\"It was rejected for a missing field we did send\"",
      cause: "Arrays travelling as encoded text in a form field can arrive empty rather than erroring. Verify the payload shape as well as its contents.",
    },
    {
      where: "Bill",
      symptom: "\"Approved, but billing still refuses the line\"",
      cause: "The approval window closed. valid_to is checked at the moment the line is added.",
    },
    {
      where: "Reconcile",
      symptom: "\"We billed the approved amount and were still short-paid\"",
      cause: "The header total was used instead of the per-item approved amounts. Partial approval is per line.",
    },
  ]}
/>

<CostTable
  rows={[
    {
      where: "Bill",
      symptom: "\"We billed against an approval and it was refused\"",
      cause:
        "The approval was withdrawn after being given. Approved is not terminal, so keep watching the reference until the work is billed.",
    },
    {
      where: "Preauth",
      symptom: "\"The request was rejected and the payload looks complete\"",
      cause:
        "A per-type required field is missing, such as the surgery date on a surgical request or the start date on a renal one, or regulation_body was sent as regulator.",
    },
    {
      where: "Preauth",
      symptom: "\"Half the items are not on the approval and we were not told\"",
      cause:
        "Extra items are dropped quietly rather than refused. Reconcile what you sent against what came back, item by item.",
    },
    {
      where: "Visit",
      symptom: "\"The approval expired before the patient came in\"",
      cause:
        "An elective approval has a window to be acted on. Store the expiry with the approval and surface it.",
    },
  ]}
/>

## Next

**Next:** [07 · Bill](/rail/stops/Bill)

<AgentPrompt>{`You are integrating an HMIS with the Savannah unified payer rail.

Task: implement stop 05, Preauth.

1. POST /api/v1/preauths with the common envelope (visit_id, request_type,
   narrative, service_start, items[], diagnosis, doctor) plus the clinical block
   required by the item's benefit type.
2. Send an idempotency_key so retries do not create duplicates.
3. Handle the decision asynchronously: have a delivery target for
   preauth.decision (the endpoint registered at stop 00, or an X-Callback-Url
   header on this request) AND poll GET /api/v1/preauths?preauth_reference= at
   poll_after_ms. Build both.
4. On approval, read next_action, it either points to a reservation call or
   straight to billing. Do not assume which.

Rules:
- Approval is PER ITEM and can be partial. Bill against approved_amount and
  approved_quantity per line, never the header total.
- Respect valid_from / valid_to. Billing outside the window needs a fresh preauth.
- A "returned" status is resubmitted under the SAME reference, not as a new one.
- Treat request_failed as retryable; it is not a decline.
- Send one item per request unless you have confirmed batching is supported.`}</AgentPrompt>
