A shipment is a courier order for transporting blood samples or lab materials from a test location to a laboratory. The Shipments API lets you create, track, modify, and cancel shipment orders.
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, or the
inverse endpoints on the appointment side. Each mutation returns the parent resource (Shipment /
Appointment) with its post-mutation link array populated.
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:
pending → in_transit → delivered
↓
fault
↓
cancelled
| Status | Meaning |
|---|
pending | The courier order has been placed and is awaiting pickup. |
in_transit | The courier has picked up the package and it is in transit. |
delivered | The package has been delivered. |
fault | A problem occurred during transit. |
cancelled | The shipment has been cancelled. |
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.
| Rule | Detail |
|---|
| Weekdays only | pickup_date must be Monday–Friday. |
| Today or future | pickup_date cannot be in the past. |
| Same-day cutoff | Same-day pickups are rejected after 16:00 Europe/Berlin. |
| Window range | pickup_time_from and pickup_time_till must be within 09:00–19:00. |
| Minimum 2h window | The pickup window must be at least 2 hours wide. |
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 a reference ID in the format PYYMMDDXXXX (e.g., P2604154821). 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:
{
"location_id": "8f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"name": "Aniva Berlin Mitte",
"street": "Unter den Linden",
"house_number": "42",
"postal_code": "10117",
"city": "Berlin",
"country": "DE",
"phone": "+4930987654321",
"email": "berlin-mitte@anivahealth.com"
}
The origin address is resolved from the test location you specified at creation time, and location_id carries that test location’s UUID — use it to correlate with List Locations. The destination address is the laboratory mapped to that location’s logistic template; location_id is null for preset destinations.
Pickup window
The pickup field in the shipment response contains the scheduled courier pickup window. It
includes from and till timestamps (ISO 8601 with the pickup-location offset, e.g.
2026-04-15T09:00:00+02:00) 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. The field is null when no pickup window has been scheduled.
Status updates log
Each shipment carries a status_updates array — a chronological debug log of every lifecycle
event reported by the logistics provider, with timestamps. Entries are oldest-first and contain:
code — the provider’s status code (e.g. GO!‘s GO10, GO90, GOY001). null when the
provider doesn’t supply one (legacy entries, internal events, or providers without a code
vocabulary). Use this for deterministic classification across language translations of status.
status — the provider’s human-readable label (e.g. "Shipment picked up",
"Loading delivery trip", "Delivered").
time — ISO 8601 timestamp of the event, as reported by the provider (typically with an
explicit timezone offset).
The list may be empty if the provider hasn’t reported any events yet.
Use the top-level status field for the canonical current state. status_updates is intended for
debugging, audit trails, and richer per-event UI.
Example shipment
{
"id": "P2604154821",
"status": "pending",
"logistics_provider": "go",
"tracking_number": null,
"origin": {
"location_id": "8f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"name": "Aniva Berlin Mitte",
"street": "Unter den Linden",
"house_number": "42",
"postal_code": "10117",
"city": "Berlin",
"country": "DE",
"phone": "+4930987654321",
"email": "berlin-mitte@anivahealth.com"
},
"destination": {
"location_id": null,
"name": "LabClinic GmbH",
"street": "Laborstraße",
"house_number": "10",
"postal_code": "80331",
"city": "München",
"country": "DE",
"phone": null,
"email": null
},
"pickup": {
"from": "2026-04-15T09:00:00+02:00",
"till": "2026-04-15T12:00:00+02:00",
"timezone": "Europe/Berlin"
},
"notes": "Handle with care — temperature-sensitive samples",
"status_updates": [],
"appointments": [
{
"id": "c9f3e2a1-7d6b-4c5e-b3a2-1f0e9d8c7b6a",
"added_at": "2026-04-03T14:30:00Z"
}
],
"created_at": "2026-04-03T14:30:00Z",
"updated_at": null
}