> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anivahealth.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shipments

> Understand how logistics shipments work in the Aniva API

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](/api/locations/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](/api/appointments/get-appointment) /
[Get Shipment](/api/shipments/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](/api/shipments/attach-appointments-to-shipment) /
[Detach Appointments from Shipment](/api/shipments/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](#pickup-schedule-rules).

<Warning>
  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`.
</Warning>

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`.

<Warning>
  Shipments cannot be cancelled after pickup. Attempts to cancel a shipment that is already
  `in_transit`, `delivered`, `fault`, or `cancelled` return `409 Conflict`.
</Warning>

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:

```json theme={null}
{
  "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](/api/locations/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 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 — 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

```json theme={null}
{
  "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-15T07:00:00Z",
    "till": "2026-04-15T10:00:00Z",
    "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
}
```
