# 07 · Bill

  The claim grows as you bill. It is not assembled afterwards from memory.

## Why this stop exists

Everything done to the patient becomes a line under the visit. The claim is built
server-side as you go, so a line can be refused at the moment it is added rather than at
submission.

This is an interchange. The eTIMS branch leaves the rail here, carrying the tax invoice and
the patient's own share.

## Add a bill line

<ApiRef method="POST" path="/api/v2/visits/{visit_ref}/bill-lines" to="/rail-api/07-bill" keep="bill_line_ref">
  Charge under an authorized service line. Name the line via `service_line_ref`; the payer
  answers with what it covers and the patient's copay.
</ApiRef>

A bill line is **payer-first**: the rail sets the charge on the payer's system, and the
payer's answer — the covered amount and the patient's copay — is what gets persisted. The
line names a `service_line_ref` (from [04 · Visit](/rail/stops/Visit)), and through it the
cover, so the charge and the authorization it draws down cannot be paired inconsistently.

```json
{
  "code": "SHA-18-005",
  "name": "Consultation, outpatient",
  "quantity": "1",
  "amount": "1500.00",
  "service_line_ref": "85ecfa5e-…",
  "practitioner_ref": "47d8e845-…"
}
```

<Wireframe
  title="Add a charge"
  actor="Billing"
  rows={[
    [
      { label: "Item", kind: "field", span: 2, from: "code + name" },
      { label: "Amount", kind: "field", span: 1, from: "amount" },
      { label: "Add", kind: "action", span: 1 },
    ],
    [
      { label: "Covered KES 1,200 · patient owes KES 300", kind: "text", span: 4, tone: "positive", from: "the payer's answer", note: "The copay is the payer's number; the patient's share is a consequence, not a declaration." },
    ],
  ]}
  caption={<>A line with no <code>service_line_ref</code> is self pay in full — no payer is asked, so it records even during a payer outage.</>}
/>

The covered and owed amounts are the payer's, never computed here. A refused line comes back
`not-billable` with the payer's verdict relayed; an unreachable payer keeps the charge
`planned` and answers `PAYER_UNREACHABLE` (502), safe to retry.

## Lines that wait

An item requiring preauthorisation comes back `held` with `next_action: preauth_pending`
instead of `none`. It sits visibly on the bill while every other line proceeds. Approval at
the previous stop releases it without touching the rest of the invoice.

Show held lines in your billing UI. A biller who cannot see what is waiting will assume it
failed and add it again.

## Combination rules run per line

Some codes cannot sit on the same visit together. The check runs on **every add-line call**,
so the biller finds out at once:

```json
{
  "error": "INVALID_CODE_COMBINATION",
  "message": "SHA-12-001 (Outpatient consultation) cannot be billed alongside SHA-07-SC-01 (Inpatient) on the same visit day.",
  "conflicting_codes": ["SHA-12-001", "SHA-07-SC-01"],
  "path": "lines[].billing_code",
  "fix_stage": "billing"
}
```

Order matters. A set of lines that is legal in one order can be refused in another, so where
your workflow permits, ask before adding rather than adding and handling the refusal.

## Documents travel with the line

Attach documents as they are captured rather than in a scramble at the end. The
`requiredDocuments` list you read at eligibility is what pre-flight checks by name at Submit.

<ApiRef method="POST" path="/api/v1/visits/{visit_id}/documents" to="/rail-api/07-bill">
  Attach a document against the visit, keyed by the document type the item requires.
</ApiRef>

  `POST /api/v1/visits/{visit_id}/level-changes` is not a registered route. The rule below is the
  one the rail is being built to; the call to make it with does not exist today.

## Accommodation and level changes

The platform generates per-diem lines itself: days multiplied by the correct tariff. Your own
day count is a cross-check rather than the source. The day of admission is day one whatever
the hour, and the counter increments at midnight.

When a patient moves between levels of care, the old line is **retired and a new one starts**,
rather than being edited in place:

<ApiRef method="POST" path="/api/v1/visits/{visit_id}/level-changes" to="/rail-api/07-bill">
  Move a per-diem line to a different level of care from a point in time.
</ApiRef>

Nothing is ever deleted. A wrong line is retired with a reason and a fresh one created, so
the audit trail behind a benefit's balance survives every correction.

## The price is not always the gazetted tariff

The level tariff you read at eligibility is only the default. Where a facility has a
**negotiated contract** with a payer, the negotiated rate applies instead. It is the largest
pricing error an integrator can make today without noticing, because a wrongly priced line
looks no different from a correct one.

<ApiRef method="GET" path="/api/v1/tariffs" to="/rail-api/07-bill">
  The price that actually applies: the gazetted level tariff, or the negotiated rate where a contract exists.
</ApiRef>

Read `source` before you read the number. It takes three values, and each one means something
different for your pricing:

<Wire
  rows={[
    { call: "GAZETTED", does: "The published level tariff applies. What you read on the procedure is what you bill.", keep: "applicable_tariff" },
    { call: "NEGOTIATED", does: "This facility has a contract for this scheme, and the level rule does not apply. Quoting the gazetted figure underbills or overbills every line.", keep: "applicable_tariff, contract_reference" },
    { call: "BILL_TO_BALANCE", does: "Public-officer covers can be billed above the tariff, against the member's balance. The tariff is not a ceiling here.", keep: "applicable_tariff, balance" },
  ]}
/>

  On most covers the tariff is the ceiling. On public-officer add-on covers the balance is the
  ceiling instead, and a provider may bill above the gazetted rate up to what the member has left.
  An integration that clamps every line to the tariff quietly under-recovers on the covers where
  there is most to recover.

## What a facility level may bill at all

Level decides more than price. Some work cannot be billed at some levels at all, and the refusal
arrives at the line rather than when the item is chosen.

<Wire
  rows={[
    { call: "Level 2", does: "Primary care, basic laboratory, maternity and outpatient public-officer services only. No per diem, no dialysis, no major surgery.", keep: "—" },
    { call: "Level 3 and above", does: "Per diem becomes billable.", keep: "—" },
    { call: "Level 4 and above", does: "Dialysis becomes billable.", keep: "—" },
    { call: "Levels 5 and 6", does: "Emergency-fund claims can only be started here.", keep: "—" },
    { call: "Level 6", does: "Everything except primary care.", keep: "—" },
  ]}
/>

Read the level from the facility registry rather than storing it, and filter what you offer by it.
A picker that lists dialysis at a level 3 site produces a rejection, after a clinician has already
spent time on it.

## Per diem counts days, and the rules are strict

Per diem has its own arithmetic rather than being a daily version of fee-for-service, and most of
the mistakes are in the counting.

<BuildSequence
  steps={[
    { do: "Day of admission is day one", detail: "Regardless of the time of day. A patient admitted at 23:40 has used a day." },
    { do: "The counter increments at midnight", detail: "Not on a rolling 24 hours. Two calendar days is two days even if it is ten hours." },
    { do: "Same-day admission and discharge is rejected", detail: "There is no zero-night per-diem claim. If the stay did not cross midnight, it is not this payment mechanism." },
    { do: "Per diem cannot be mixed with a surgical package", detail: "Choose one. The combination is refused, and the refusal names codes rather than mechanisms." },
    { do: "A per-diem line cannot be removed once added", detail: "Correcting it means correcting the visit, not deleting the line. Add it deliberately." },
  ]}
/>

Length-of-stay caps are per benefit, and some are **per household per year** rather than per visit,
so a relative's admission can shorten what this patient may claim.

## Surgery carries a global period

A surgical claim covers a window of care rather than a single act, and inside that window some
rebates cannot be billed at all.

<Wire
  rows={[
    { call: "Minor surgery", does: "One night", keep: "global period" },
    { call: "Major surgery", does: "Three nights", keep: "global period" },
    { call: "Specialised surgery", does: "Seven nights", keep: "global period" },
  ]}
/>

Inside the global period, **medical inpatient, critical care, mental health and palliative rebates
cannot be billed.** Beyond it, further nights are billed at the gazetted rebate rate and the
admission has to be updated to a complication. Admission, procedure and discharge dates are all
required on a surgical claim.

Two things can still be claimed separately: an **unplanned return to theatre**, and care unrelated
to the original procedure. The claim has to be able to tell them apart, so do not fold either one
into the original line.

  Moving a patient between inpatient and critical care, or inpatient and palliative, and back again,
  is allowed, and each period is priced at its own tariff. The admission is not one stay at the
  higher rate. If your billing model holds a single rate for the admission, switching will misprice it.

## Money is held as you bill

<Wire
  rows={[
    { call: "held", does: "Committed against the benefit, visible on the working ledger", keep: "reservation_id" },
    { call: "consumed", does: "The claim was submitted, so the hold became actual spend", keep: "—" },
    { call: "released", does: "The line was retired or the visit cancelled, and the amount went back to the benefit", keep: "—" },
    { call: "expired", does: "The reservation outlived its maximum age", keep: "—" },
  ]}
/>

  Not every payer holds money in a ledger you can see. Where balances are tracked externally,
  what you read as an available balance is a live read of the payer's own figure rather than
  a local reservation. The contract you code against is the same either way: read
  `reservation_id` off the line if it is there, and do not build logic that assumes it always
  will be.

## Every line carries its scheme code

A claim line is billed against a fund, and the **scheme code on the line is what routes it there.**
Lines on one visit can legitimately carry different scheme codes, because a member's covers differ
by benefit and the cover can be chosen per item.

Treat it as required on every line rather than as an override you occasionally set. A missing or
wrong scheme code routes the line to the wrong fund, and comes back looking like an assessment
dispute rather than a routing mistake.

## Diagnoses, prescriptions and switching

These three rules reject claims and are easy to miss.

<Wire
  rows={[
    { call: "One primary diagnosis per procedure", does: "Not one per claim. A standard claim needs at least one primary diagnosis against each procedure, and duplicate codes on the same claim are rejected.", keep: "the diagnosis per line" },
    { call: "Primary care takes exactly one", does: "Two primary diagnoses on a primary care claim is a rejection, and the diagnosis catalogue is filtered to primary-care-applicable codes for those claims.", keep: "—" },
    { call: "Prescriptions come from the national catalogue", does: "Dispensed quantity cannot exceed prescribed quantity, and some visit types exclude prescriptions entirely.", keep: "the catalogue code" },
  ]}
/>

**Switching between levels of care is priced per period.** Moving a patient from inpatient to
critical care and back, or inpatient to palliative, is allowed, and each period is priced at its own
tariff rather than the whole stay at the higher one.

  A few of these, prescription and diagnosis rules on primary care claims in particular, may
  currently be enforced in the interface rather than in the API. An integration that does not use
  that interface is not stopped today, and would start failing when the check moves. Build to the
  rule rather than to what the environment currently lets through.

## What breaks here

<CostTable
  rows={[
    {
      where: "Bill",
      symptom: "\"The line was refused and the codes look fine individually\"",
      cause: "A combination rule. The conflict is with another line already on the visit, and the message names both.",
    },
    {
      where: "Bill",
      symptom: "\"The price came back different from our price list\"",
      cause: "The rail resolves pricing from tariff, facility level and cover rather than from what you sent. Your price list is for your own invoice, not this one.",
    },
    {
      where: "Bill",
      symptom: "\"We cannot edit the accommodation line\"",
      cause: "Per-diem lines are retired through a level change, never edited. Editing is not a supported operation.",
    },
    {
      where: "Submit",
      symptom: "\"Unmapped code\"",
      cause: "The item was never declared in mapping, so it has no path to a payer revenue code. Anything undeclared stays off the claim entirely.",
    },
  ]}
/>

<CostTable
  rows={[
    {
      where: "Reconciliation",
      symptom: "\"Every line at this facility is paid at a different amount than we billed\"",
      cause:
        "A negotiated contract exists and the gazetted level tariff was used. Read source from the tariff call before pricing anything.",
    },
    {
      where: "Reconciliation",
      symptom: "\"We under-recovered on the public-officer covers\"",
      cause:
        "Lines were clamped to the tariff. On those covers the balance is the ceiling, not the tariff.",
    },
    {
      where: "Bill",
      symptom: "\"Dialysis was refused and the code is right\"",
      cause:
        "Facility level. Dialysis is level 4 and above; per diem is level 3 and above. The picker offered something the site cannot bill.",
    },
    {
      where: "Bill",
      symptom: "\"The per-diem claim was rejected for a same-day discharge\"",
      cause:
        "There is no zero-night per diem. The day of admission is day one and the counter moves at midnight.",
    },
    {
      where: "Bill",
      symptom: "\"We cannot bill the ICU days inside the surgical window\"",
      cause:
        "Global period. Critical care, medical inpatient, mental health and palliative rebates are excluded for one, three or seven nights depending on the surgery.",
    },
  ]}
/>

### Errors

| HTTP | `error` | When |
|------|---------|------|
| 422 | `UNMAPPED_CODE` | The code does not resolve to a live, mapped item. |
| 422 | `INVALID_CODE_COMBINATION` | The line conflicts with another active line. |
| 409 | `LINE_NOT_RETIRABLE` | Retire attempted on a per-diem line outside a level change. |
| 409 | `RESERVATION_EXCEEDED` | The amount exceeds what is available at the moment of the call. |

## The branch leaves here

Billing produces obligations the payer never sees: the tax invoice, and whatever the
patient pays themselves. Those leave the rail at this stop:

- **[Invoice · eTIMS](/docs/eTIMS/Getting-Started)**, for sending the tax invoice to KRA
- **[Collect · Slade Advantage](/docs/Slade-Advantage/Getting-Started)**, for taking the patient's share

## Next

**Next:** [08 · Submit](/rail/stops/Submit)
