Skip to main content
A shipment is one physical parcel: a courier collection carrying blood samples from a test location to a laboratory, the kit on its way to a home-kit patient, or the sample that patient posts back. The Shipments API lets you create, track, modify, and cancel shipment orders, and lists every parcel in one place whoever booked it.

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 off destination.
  • booked_byaniva, probatix or biomes: who printed the label. Only aniva shipments accept Update and Cancel; a parcel a fulfilment partner booked is tracked in the same list but returns 501 on either.
A parcel that carried a return label and the parcel that label brings back point at each other through 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 stamps synced_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 origin location_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 appointments array of { id, added_at } entries;
  • on the appointment, as a shipments array of { id, added_at } entries.
Both arrays are oldest-link first. Each 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 using PATCH /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.
Shipments cannot be modified after the courier has picked up the package. Attempts to update a shipment in in_transit, delivered, or fault status return 409 Conflict.
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 using DELETE /api/v1/shipments/{id}. This calls the logistics provider API to cancel the order and updates the status to cancelled.
Shipments cannot be cancelled after pickup. Attempts to cancel a shipment that is already in_transit, delivered, fault, or cancelled return 409 Conflict.
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

The origin and destination fields in the shipment response contain resolved address objects with the following structure:
For a practice parcel the 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

The pickup 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 a status_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. null when the event carries no state (label creation, a carrier “no information” scan, entries recorded before the field existed). Never cancelled — a cancellation is ours, not a carrier event.
  • code — the carrier’s own status token (GO!‘s GO10, GO90, GOY001; DHL’s VA, ZF, LA, ZU). null when the carrier supplied none. Stable across language translations of status.
  • detail_code — the carrier’s finer token where it has one: DHL’s statusDetailed, e.g. CNRFC_NRQRD_ZF (redirected to a post office), HLDCC_SERPT_LA (ready for pickup there), DLVRD_SERPT_ZU (picked up). null elsewhere.
  • 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. null when 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).
The list may be empty if the carrier hasn’t reported any events yet. Use the top-level 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

The return-leg row exists from the moment the return label is printed and stays pending until the patient posts the sample — that is the honest state, not a gap.