TrueRoute Developers

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 refused with the new appointment reason vendor_unavailable: nothing was booked, and the same journey can be booked again in a few minutes. slot_taken still means the time is gone (list slots again); vendor_refused still means the vendor declined the booking. Before 2.9.0 every one of these read vendor_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 code order_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 make diagnostics.order_cancel answer order_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.book also lists appointment_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 is cancel_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; otherwise order_not_cancellable (a lab-cancelled order included). The lab order moves to cancelled with reason cancelled_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 answers vendor_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/catalog is MOUNTED (it was published with implemented: false and answered 404). It refuses an unknown lab (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).
  • LabCatalogTest adds kind (panel | test), memberTestIds, patientName, patientDescription, category (LAB_CATALOG_CATEGORIES), patientPrep (PatientPrepItem[], codes PATIENT_PREP_CODES) and eligibility (LabCatalogEligibility: sex, minAge, maxAge).
  • The quote judges the eligibility of a row against the patient on file: new ineligibleReasons patient_sex_not_eligible and patient_age_not_eligible, and test_not_collectable_on_rail when the rails of a row exclude the requested rail. A restricted row quoted with patientLocation only, or for a patient whose sex at birth is unknown, adds the new unknownChecks value patient_eligibility and the quote is unknown, 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 pharmacy alone (the pharmacy contract, unchanged), and a new one defaults to every family. Every event is written in the same transaction as its fact; the envelope sequence of a journey event IS its journeySequence (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.eventDeliveredAt is now set when an endpoint first answers 2xx to the diagnostics.result.critical event.
  • 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) and MODULE_WEBHOOK_MACHINE_EVENTS (which journey machine statuses announce which event types; the others announce nothing). unmatchedResultId is the resultId of 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 with overlapEndsAt, a second rotation during an overlap is refused, and platform.webhook_secret.rotation_started is delivered to that endpoint signed under both secrets. An endpoint that receives pharmacy alone keeps the in-place rotation of the pharmacy contract and receives no platform event.

2.3.0 2026-10-08

  • New refused code npi_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 of clinician_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_SCOPES plus prescribers:write): the pharmacy contract's prescriber sync (POST /v1/integrations/prescribers, prescriber sync contract 1.0.0) may then name that granted clinic in clinicId. No module route answers to it, and ClinicMappingView.scopes lists module scopes only, so the grant's prescribers:write never appears in a module response.

2.2.0 2026-10-08

  • Mounted (implemented: true): invoices.list and invoices.read. Every diagnostic order now writes its ModuleInvoiceView record in the same transaction, in both billing modes (DEC-055; collectedBy: consumer under passthrough). No money moves: state is issued until the record is settled outside TrueRoute, then settled with settledAt and settlementReference; credited and credit_note records arrive with the cancellation and refund flows. invoices.list answers records issued in [from, to): one end may be open (to defaults to now, from to to minus 31 days); both open, reversed or longer than 31 days is invalid_time_window.
  • The platform_fee line 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 EXISTING lab_bill_type_not_permitted (or lab_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 refused codes 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) and critical_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 existing patient_state_not_served; a clinician without an in-date licence there answers the existing clinician_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; reEscalationCount and nextReEscalationAt move as TrueRoute re-escalates (every 30 minutes, to criticalReEscalationTarget, while no statement is recorded); firstEscalatedAt is when a TrueRoute operator first reached that target. eventDeliveredAt stays 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. revision is per RESULT REPORT (resultId, which TrueRoute assigns to one lab order, placerOrderNumber, and one lab report, labReportId), not per order; supersedesRevision stays within that report. ResultSummary and ResultRevision gain placerOrderNumber, labReportId and journeySequence (the journey event sequence that admitted the revision, for ordering across reports); DocumentMetadataView gains resultId. Key a result ledger on (resultId, revision).
  • SBX-CORRECTED names lab_explicit, the typed correction signal (LAB_CORRECTION_SIGNALS); 1.0.0 said vendor_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 owner consumer, 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.escalation gains acknowledgements[]; acknowledgedBy.clinicianId becomes nullable; followUpAction is the clinical follow-up only. New code critical_ack_clinician_invalid.
  • The critical:acknowledge scope text matches the route: escalation owner consumer under customer_supplied, and under trueeval_supplied on a trueEvalOwnedConsumer integration.
  • Breaking. CriticalFactView gains criticalReEscalationTarget; CriticalFactView.escalation gains the clock fields clockStartedAt and reEscalationCount.
  • 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 a v1= signature under the new AND the old secret, announced by the new event platform.webhook_secret.rotation_started (its data keys endpointId and overlapEndsAt are new webhook data keys).
  • Breaking. priority is routine | stat on the quote and the order (widened). stat is honoured only where RailCapabilities.statPriority is true and is refused rail_capability_unavailable elsewhere. RailCapabilities gains statPriority.
  • New scope patients:read for the two patient-reference reads; patients:write no longer covers them.
  • Stated: the signed t= in x-trueroute-signature is authoritative; x-trueroute-timestamp is 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 to PrescriberSyncRequest on 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 carries prescribers:write; otherwise 403 prescriber_clinic_not_granted (no grant in force for that clinic) or 403 prescriber_grant_scope_missing (a grant in force without prescribers:write). The status read takes the same optional clinicId as 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.