Skip to main content
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
StatusMeaning
pendingThe courier order has been placed and is awaiting pickup.
in_transitThe courier has picked up the package and it is in transit.
deliveredThe package has been delivered.
faultA problem occurred during transit.
cancelledThe 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.
RuleDetail
Weekdays onlypickup_date must be Monday–Friday.
Today or futurepickup_date cannot be in the past.
Same-day cutoffSame-day pickups are rejected after 16:00 Europe/Berlin.
Window rangepickup_time_from and pickup_time_till must be within 09:00–19:00.
Minimum 2h windowThe 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
}