Changelog
Each contract has its own semantic version. A minor release only adds; a breaking change is a new major published beside the old one. Current versions: reference.
Modules contract
2.9.0 2026-10-10
- A mobile booking the vendor could not take because it could not be reached, or answered in a way TrueRoute could not read, BEFORE the booking was sent (or rate-limited it), is now
refusedwith the new appointment reasonvendor_unavailable: nothing was booked, and the same journey can be booked again in a few minutes.slot_takenstill means the time is gone (list slots again);vendor_refusedstill means the vendor declined the booking. Before 2.9.0 every one of these readvendor_refused. - 1 change specific to a lab or collection rail, described in the modules handoff provided with your credentials.
2.8.0 2026-10-10
- POST appointments (
collection.book) and POST appointments/:appointmentId/reschedule (collection.reschedule) refuse a journey whose lab order is closed with the new codeorder_closed(422, refused): the order was cancelled, refused by the lab, or has resulted, or its specimen is already with the lab (the same facts that makediagnostics.order_cancelanswerorder_not_cancellable, or a repeated cancel answer the cancelled journey). Nothing is booked. Before 2.8.0 a visit could be booked on a cancelled order.collection.bookalso listsappointment_not_modifiable, which it already answered when the journey has an open appointment.
2.7.0 2026-10-09
- POST orders/cancel (
diagnostics.order_cancel) is mounted. Name exactly one of journeyId and externalOrderId (an absent id reads as null; neither or both iscancel_target_ambiguous). Cancellable while the lab order is pending, accepted or ambiguous, every specimen is still awaiting, nothing has resulted, and collection has not reached checked_in, collected or kit_in_transit_to_lab; otherwiseorder_not_cancellable(a lab-cancelled order included). The lab order moves tocancelledwith reasoncancelled_by_clinic, and an open collection appointment is cancelled with it (cancelled_by_clinic), each announced by the webhooks 2.1.0 published. Repeating the cancel of an order the clinic already cancelled answers 200 with the cancelled journey. When a booked mobile draw vendor cannot confirm, the route answersvendor_unavailable(an existing code): the lab order is already cancelled, and a new cancel (new Idempotency-Key) asks the vendor again. A money of a TrueRoute-billed order is not moved by this route (pass-through orders are billed by the consumer).
2.6.0 2026-10-09
- 1 change specific to a lab or collection rail, described in the modules handoff provided with your credentials.
2.5.0 2026-10-09
GET /v1/modules/diagnostics/catalogis MOUNTED (it was published withimplemented: falseand answered 404). It refuses an unknownlab(missing_field), a clinic without the diagnostics module (module_not_enabled), a suspended module or lab rail (module_suspended) and a clinic without the lab rail (rail_not_enabled).LabCatalogTestaddskind(panel|test),memberTestIds,patientName,patientDescription,category(LAB_CATALOG_CATEGORIES),patientPrep(PatientPrepItem[], codesPATIENT_PREP_CODES) andeligibility(LabCatalogEligibility: sex, minAge, maxAge).- The quote judges the eligibility of a row against the patient on file: new
ineligibleReasonspatient_sex_not_eligibleandpatient_age_not_eligible, andtest_not_collectable_on_railwhen the rails of a row exclude the requested rail. A restricted row quoted withpatientLocationonly, or for a patient whose sex at birth isunknown, adds the newunknownChecksvaluepatient_eligibilityand the quote isunknown, never eligible. - 1 change specific to a lab or collection rail, described in the modules handoff provided with your credentials.
2.4.0 2026-10-08
- Module webhook events are DELIVERED (S8), through the pharmacy webhook worker and to the same operator-registered endpoints. An endpoint records its event families; one registered before modules existed keeps
pharmacyalone (the pharmacy contract, unchanged), and a new one defaults to every family. Every event is written in the same transaction as its fact; the envelopesequenceof a journey event IS itsjourneySequence(critical acknowledgements and re-escalations take the next journey sequence too), so a status no event type announces leaves a gap, never a reuse.CriticalFactView.escalation.eventDeliveredAtis now set when an endpoint first answers 2xx to thediagnostics.result.criticalevent. - Stated:
MODULE_WEBHOOK_DELIVERY_SCOPES(the grant scope the clinic of an event must still carry for the integration when the event is CLAIMED for delivery; an event whose clinic no longer qualifies is withheld for good, never sent) andMODULE_WEBHOOK_MACHINE_EVENTS(which journey machine statuses announce which event types; the others announce nothing).unmatchedResultIdis theresultIdof the unbound report. A sandbox event goes only to a sandbox endpoint and a live event only to a live endpoint. - Secret rotation (
MODULE_WEBHOOK_SECRET_ROTATION) is live for every endpoint that receives a module family: the operator rotation answers the new secret once withoverlapEndsAt, a second rotation during an overlap is refused, andplatform.webhook_secret.rotation_startedis delivered to that endpoint signed under both secrets. An endpoint that receivespharmacyalone keeps the in-place rotation of the pharmacy contract and receives no platform event.
2.3.0 2026-10-08
- New
refusedcodenpi_verification_synthetic_not_live(422): the ordering clinician's NPI-1 answer is a SYNTHETIC sandbox verification (recorded by a TrueRoute operator for a provider of a quarantined demo clinic on a non-production host, never from the NPI Registry), and the request is on a LIVE path (a live key, a production host, or a clinic that is no longer a demo clinic). The diagnostic order answers it in place ofclinician_not_authorized; a sandbox key on a demo clinic counts the synthetic answer as NPI-1. - A partner clinic grant may carry
prescribers:write(PARTNER_GRANT_SCOPES=MODULE_SCOPESplusprescribers:write): the pharmacy contract's prescriber sync (POST /v1/integrations/prescribers, prescriber sync contract 1.0.0) may then name that granted clinic inclinicId. No module route answers to it, andClinicMappingView.scopeslists module scopes only, so the grant'sprescribers:writenever appears in a module response.
2.2.0 2026-10-08
- Mounted (implemented: true):
invoices.listandinvoices.read. Every diagnostic order now writes itsModuleInvoiceViewrecord in the same transaction, in both billing modes (DEC-055;collectedBy: consumerunder passthrough). No money moves:stateisissueduntil the record is settled outside TrueRoute, thensettledwithsettledAtandsettlementReference;creditedandcredit_noterecords arrive with the cancellation and refund flows.invoices.listanswers records issued in [from, to): one end may be open (to defaults to now, from totominus 31 days); both open, reversed or longer than 31 days isinvalid_time_window. - The
platform_feeline of a quote, an order and an invoice is ONE flat amount per order from the versioned module fee table (never per test, never a percentage), and is 0 until a fee version is recorded; the sandbox no longer shows its placeholder fee. The quote and the order answer the EXISTINGlab_bill_type_not_permitted(orlab_billing_conditions_unmet) when the arrangement makes the clinic or the consumer the payer for a patient in a state where the lab may not client-bill (NY and NJ: only the patient or the insurer pays for a lab test).
2.1.0 2026-10-08
- Four new
refusedcodes for the fail-closed lab compliance rules (research findings R2-R10), each published before any release emits it:lab_bill_type_not_permitted(the lab may not bill the bill type of the arrangement for a patient in that state, or the state is not in the policy table),lab_billing_conditions_unmet(the state allows that bill type only with pass-through pricing and the performing lab disclosed, and the arrangement records neither),authority_mode_state_restricted(the trueeval_supplied mode is held for that patient state until counsel rules) andcritical_contact_missing(the ordering clinician has no 24/7 critical contact on record, CLIA 493.1291(g)). A lab that refuses the state of the patient answers the existingpatient_state_not_served; a clinician without an in-date licence there answers the existingclinician_licence_state. - Mounted (implemented: true): the patient reference routes (create, read, read by external id, demographics append), the diagnostic quote and quote read, order and both order reads, the results list, the result read and the revision read, and the sandbox advance route (sandbox host only).
- Mounted (implemented: true): the critical fact read and the critical acknowledgement statement. The first statement stops the acknowledgement clock;
reEscalationCountandnextReEscalationAtmove as TrueRoute re-escalates (every 30 minutes, tocriticalReEscalationTarget, while no statement is recorded);firstEscalatedAtis when a TrueRoute operator first reached that target.eventDeliveredAtstays null until the module webhook rail delivers the critical events.
2.0.0 2026-10-07
- The modules contract imports nothing from the pharmacy contract: the primitives both share live in the import-free
src/contracts/shared/primitives.ts. - Breaking.
revisionis per RESULT REPORT (resultId, which TrueRoute assigns to one lab order,placerOrderNumber, and one lab report,labReportId), not per order;supersedesRevisionstays within that report.ResultSummaryandResultRevisiongainplacerOrderNumber,labReportIdandjourneySequence(the journey event sequence that admitted the revision, for ordering across reports);DocumentMetadataViewgainsresultId. Key a result ledger on (resultId, revision). - SBX-CORRECTED names
lab_explicit, the typed correction signal (LAB_CORRECTION_SIGNALS); 1.0.0 saidvendor_explicit, a value the type never had. - Breaking. A critical acknowledgement is a sequence of STATEMENTS. New action
escalated_to_responsible_clinician: the consumer, as escalation owner, has delivered the critical to its responsible clinician, a receipt and delivery statement and never a clinical one. It may name no clinician, is allowed only for escalation ownerconsumer, is the documented first acknowledgement there, and stops the re-escalation clock. Clinical follow-ups (CRITICAL_CLINICAL_FOLLOW_UP_ACTIONS) stay separate, later statements that name the clinician.CriticalFactView.escalationgainsacknowledgements[];acknowledgedBy.clinicianIdbecomes nullable;followUpActionis the clinical follow-up only. New codecritical_ack_clinician_invalid. - The
critical:acknowledgescope text matches the route: escalation ownerconsumerunder customer_supplied, and under trueeval_supplied on a trueEvalOwnedConsumer integration. - Breaking.
CriticalFactViewgainscriticalReEscalationTarget;CriticalFactView.escalationgains the clock fieldsclockStartedAtandreEscalationCount. - Module events use the same operator-registered webhook endpoints as pharmacy events. An endpoint subscribes to event FAMILIES (
MODULE_WEBHOOK_FAMILIES) or to all; one integration may register one endpoint for everything or one per family, each with its own secret. - Breaking. Published secret rotation (
MODULE_WEBHOOK_SECRET_ROTATION): operator-triggered, a 7-day overlap during which every delivery carries av1=signature under the new AND the old secret, announced by the new eventplatform.webhook_secret.rotation_started(its data keysendpointIdandoverlapEndsAtare new webhook data keys). - Breaking.
priorityisroutine | staton the quote and the order (widened).statis honoured only whereRailCapabilities.statPriorityis true and is refusedrail_capability_unavailableelsewhere.RailCapabilitiesgainsstatPriority. - New scope
patients:readfor the two patient-reference reads;patients:writeno longer covers them. - Stated: the signed
t=inx-trueroute-signatureis authoritative;x-trueroute-timestampis informational, kept for pharmacy parity, and loses if the two differ. - Synthetic example payloads for every webhook event type and the main read shapes, in the handoff (section 17) and the manifest (
examples), typed against the contract (src/contracts/modulesExamples.ts).
Prescriber sync contract
1.0.0 2026-10-08
- DRIFT CLOSED BY THIS PIN:
email(optional) was added toPrescriberSyncRequeston 2026-10-07 (TR-521, S3) without a version change. It is the prescriber's own email, checked by the shared address rule and written only onto a provider the sync creates. 1.0.0 records it. clinicId(optional, a TrueRoute clinic id): the clinic the prescriber is synced to. Omitted: the credential's own clinic, unchanged. Named: accepted only when it is the credential's own clinic or a leaf clinic this integration holds an in-window partner grant for that carriesprescribers:write; otherwise 403prescriber_clinic_not_granted(no grant in force for that clinic) or 403prescriber_grant_scope_missing(a grant in force withoutprescribers:write). The status read takes the same optionalclinicIdas a query parameter. One upstream prescriber may be synced to several clinics; under one integration an NPI still names one upstream id (prescriber_identity_collision). A sync never enrols anyone at a pharmacy.