---
title: Shipment lifecycle
description: Separate draft creation, booking acceptance, and completed booking.
---

Creating a shipment saves a draft. Booking is a separate request: the API accepts it, then a background worker contacts the carrier. Read the shipment to find the completed result.

## Lifecycle at a glance

This diagram shows the normal flow for carrier shipments. Cancellation is available only when the shipment meets the eligibility rules below.

<div
  className="shipment-lifecycle not-prose"
  role="region"
  aria-label="Shipment lifecycle, from draft to delivery"
  tabIndex={0}
>
  <ol>
    <li>
      <code>PENDING</code>
      <strong>Draft saved</strong>
      <span>Create a draft</span>
    </li>
    <li>
      <code>PROCESSING</code>
      <strong>Booking accepted</strong>
      <span>Background work</span>
    </li>
    <li>
      <code>BOOKED</code>
      <strong>Booking completed</strong>
      <span>Carrier booking succeeds</span>
    </li>
    <li>
      <code>PICKED_UP</code>
      <strong>Picked up</strong>
      <span>Pickup is recorded</span>
    </li>
    <li>
      <code>DELIVERED</code>
      <strong>Delivered</strong>
      <span>Delivery is recorded</span>
    </li>
  </ol>
</div>

- **Booking failure:** `PROCESSING` → `FAILED`. Read the failure code and message.
- **Cancellation:** eligible `PENDING` or `BOOKED` → `CANCELED`. See the cancellation rules below.

| Stage             | What the response confirms                                                                                                          | What to do next                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| Draft saved       | `POST /shipments` returns `201` and `PENDING`. No carrier booking has been requested.                                               | Review or update the draft, then request booking.            |
| Booking accepted  | `POST /shipments/{id}/book` returns `200` and `PROCESSING`. The request has been accepted and dispatched for background processing. | Read `GET /shipments/{id}` for the result.                   |
| Booking completed | A shipment read reports `BOOKED`. Background booking has succeeded.                                                                 | Read tracking and request an existing label when ready.      |
| Booking failed    | A shipment read reports `FAILED` with a `failure` code and message.                                                                 | Inspect the failure details before deciding the next action. |

`OTHER` shipments do not follow the carrier-booking flow and cannot have printable labels.

## Create or update a draft

`POST /shipments` returns `201` with a priced `PENDING` draft and stored address snapshots. It does not book. `PUT /shipments/{id}` returns `200` after updating and repricing an eligible draft. Updating and booking require `PENDING` and exclude shipments linked to payments, quotations, proforma data, or automatic returns.

The reference uses GLS Business Parcel with one package. Use a service and package type returned by service discovery for your account. Supply the required package/contact fields and a valid pickup date on the Copenhagen calendar; carrier dates cannot be in the past.

For `SHOP_DELIVERY`, [select a GLS pickup location](/guides/pickup-locations) before previewing a price or creating the draft. Price and shipment requests need only `pickupLocation: { externalLocationId }`. Bookogsend supplies the shop details.

## Request booking and read the result

Send `{}` to `POST /shipments/{id}/book` with Authorization, JSON Content-Type, and a new `Idempotency-Key`. Booking validates the stored draft and recalculates its price.

`200` with `PROCESSING` confirms booking acceptance and queue dispatch. It does not confirm completed carrier booking. Read `GET /shipments/{id}` at a modest interval to observe progress, within the [account rate limit](/guides/retries#rate-limits). A later read reports `BOOKED` or `FAILED`. Failed shipments include a safe `failure` code and message. Shipment reads include tracking numbers and package delivery states; pickup and delivery updates appear as they are recorded.

`409 PRICE_CHANGED` leaves the draft `PENDING`, updates its stored price, and returns `errors.price`. Review the new price and use a **new idempotency key** to accept it. The old key replays the price conflict. `409 SERVICE_NO_LONGER_AVAILABLE` queues nothing.

## Cancel a shipment

Send `{}` to `POST /shipments/{id}/cancel` with an idempotency key. Eligible `PENDING` or `BOOKED` shipments become `CANCELED` and remain stored. Already canceled shipments succeed.

Cancellation can be rejected with `409 CANCELLATION_NOT_ALLOWED`; `errors.currentStatus` reports the shipment status. Success confirms committed cancellation. Notifications and fulfillment reconciliation run afterward in the background.

## Download an existing label

`GET /shipments/{id}/label` requires `SHIPMENTS_READ` and returns `302` with a `Location` header pointing to a PDF URL signed for five minutes. Follow the redirect in your HTTP client. Request a fresh URL when it expires. This response has no JSON success envelope.

Labels require a printable service, a `BOOKED`, `PICKED_UP`, or `DELIVERED` state, and an existing PDF. `labelReady` in reads is a lifecycle hint; it does not verify the PDF exists. The endpoint returns `409 LABEL_NOT_READY` for an unready/missing label and `400 LABEL_NOT_SUPPORTED` for an unsupported service. It never books or regenerates a label.
