One list, filtered
GET /api/v1/shipments is the only collection. A shipment stands alone — it is many-to-many with appointments and may belong to a home-kit order — so there is no list nested under a parent. Narrow the same list with ?appointment_id= or ?home_kit_order_id=, paginate it with limit / offset, and follow a parent’s inline shipments array ({ id, added_at } on both appointments and home-kit orders) to Get Shipment when you hold one id.
Contents, and who booked it
Every shipment carries two facts that are not visible from its addresses:contents— free text describing what is in the parcel, set at creation ("Medizinisch freigestellte Probe","Home test kit") and shown to the courier. Informational only. Direction is not a field: whether a parcel is heading to a laboratory is read offdestination.booked_by—aniva,probatixorbiomes: who printed the label. Onlyanivashipments accept Update and Cancel; a parcel a fulfilment partner booked is tracked in the same list but returns501on either.
paired_with.
Reads never call the carrier
Every read is served from our database. A background sync reconciles each shipment with its carrier (every 10 minutes for GO!, hourly for DHL) and stampssynced_at, which is how old any answer is. There is no force-refresh — DHL allows one tracking call every five seconds, so a read that paid the carrier round trip could not be offered.
Creating a shipment
When creating a shipment, you specify the originlocation_id — the test location where the courier should pick up. The destination laboratory is automatically determined based on the location’s configuration.
The location must be within your API key’s access context. Use List Locations to find available location IDs.
You can optionally pass appointment_id to link the shipment to a specific appointment at
creation time. The link surfaces inline on both sides:
- on the shipment, as an
appointmentsarray of{ id, added_at }entries; - on the appointment, as a
shipmentsarray of{ id, added_at }entries.
id cross-references the canonical detail endpoint
(Get Appointment /
Get Shipment) for the full payload — partners who want
per-appointment courier progression follow each entry in the appointment’s shipments array to
the shipment detail endpoint.
A shipment can be linked to multiple appointments, and an appointment can be linked to multiple
shipments. After creation, manage the linkage with Attach Appointments to
Shipment /
Detach Appointments from Shipment. Each
mutation returns the shipment’s post-mutation appointment list.
The mutation rule is: links can be created or removed when EITHER the appointment is past
blood-draw (blood_drawn), or the shipment was created less than 24 hours ago. Outside that
window the link is locked so historical records stay stable.
Lifecycle
Shipments move through the following statuses:Pickup schedule rules
The following rules apply when creating or updating a shipment’s pickup schedule.pickup_date
(YYYY-MM-DD) and pickup_time_from / pickup_time_till (HH:MM, 24h) are wall-clock values
interpreted in the origin location’s IANA timezone — you do not send a timezone alongside them.
Modifying shipments
You can update a shipment’s pickup schedule, package count, weight, and notes usingPATCH /api/v1/shipments/{id}. If any logistics-relevant fields change (pickup date, time, package count, or weight), the existing logistics order is automatically cancelled and a new one is created. Fields you omit from the update body are preserved from the existing shipment — for example, updating only weight will re-create the order with the same pickup schedule. Updated schedule fields are validated against the same pickup schedule rules.
If the logistics provider rejects the cancellation or re-creation during an update, the API returns 502 Bad Gateway with the provider’s error message.
Cancelling shipments
Cancel a shipment usingDELETE /api/v1/shipments/{id}. This calls the logistics provider API to cancel the order and updates the status to cancelled.
The logistics provider may also reject the cancellation for its own reasons (e.g., the cancellation deadline has passed). In that case, the API returns 502 Bad Gateway with the provider’s error message and error code.
Shipment reference IDs
Each shipment is assigned an 11-character reference ID,{letter}YYMMDDXXXX (e.g., P2604154821). The letter names who created the row — P for shipments you create through this API, M for the Aniva team, C for Aniva’s daily dispatch, K for a home-kit fulfilment partner — never a direction or a leg; the date is the creator’s own day. Use this ID to retrieve, update, or cancel the shipment.
Shipment addresses
Theorigin and destination fields in the shipment response contain resolved address objects with the following structure:
origin address is resolved from the test location you specified at
creation time and the destination is the laboratory mapped to that location’s logistic template.
For a home-kit parcel the patient’s delivery address is the destination of the kit and the
origin of the sample return; the fulfilment partner’s warehouse is the kit’s origin. The
address carries no location_id — correlate a practice parcel through the appointment it is
linked to.
Pickup window
Thepickup field in the shipment response contains the scheduled courier pickup window. It
includes from and till timestamps as UTC instants (ISO 8601 with Z suffix, e.g.
2026-04-15T07:00:00Z) and a timezone string (IANA timezone of the pickup location). The
wall-clock pickup_date / pickup_time_* you sent at create / update time is resolved into these
absolute instants using that timezone — use timezone to render from / till back in the
pickup-location wall-clock. The field is null when no pickup window has been scheduled.
Status updates log
Each shipment carries astatus_updates array — every lifecycle event the logistics provider
reported, oldest first. Each entry contains:
state— the event in the shipment status vocabulary (pending,in_transit,delivered,fault), so you can read a timeline without knowing the carrier’s codes.nullwhen the event carries no state (label creation, a carrier “no information” scan, entries recorded before the field existed). Nevercancelled— a cancellation is ours, not a carrier event.code— the carrier’s own status token (GO!‘sGO10,GO90,GOY001; DHL’sVA,ZF,LA,ZU).nullwhen the carrier supplied none. Stable across language translations ofstatus.detail_code— the carrier’s finer token where it has one: DHL’sstatusDetailed, e.g.CNRFC_NRQRD_ZF(redirected to a post office),HLDCC_SERPT_LA(ready for pickup there),DLVRD_SERPT_ZU(picked up).nullelsewhere.status— the carrier’s human-readable label (e.g."Shipment picked up","Die Sendung wurde abgeholt.").description— an explanation of the code, or the carrier’s longer sentence.nullwhen neither exists.location— where the scan happened, as the carrier names it: a GO! depot, a DHL city. Free text, not a coordinate.time— ISO 8601 timestamp of the event, as reported by the carrier (with its offset).
status field for the canonical current state. It moves one way — pending
to in_transit to delivered — with fault overlaid while the newest event reports one;
carriers do not order their events by significance (DHL appends a pre-transit-coded redirect
advice after the handover scan), so the newest entry alone does not say where a parcel stands.
Example shipment
pending until the patient posts the sample — that is the honest state, not a gap.