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

# Create a Shipment

> Register a Shipment in Perform.AI — from a minimal Open Shipment to a fully tracked, enriched record.

Creating a Shipment registers it in your account so Perform.AI can track it, notify recipients, and surface it across the platform. This guide covers the workflow and the rules that trip integrators up; every field is specified in the [API reference](/api-reference/v5/shipments/create-shipment) and the [field reference](/essentials/field-reference).

## Prerequisites

* A [Bearer token](/authentication).
* To enable tracking: a carrier reference configured under **Settings > Carriers**. Check what's configured with the Carrier Configurations API.

## The minimum Shipment

Only `shipment_id` is required:

```bash theme={null}
curl --request POST \
  --url https://api.perform.ai/v5/shipment/ \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "shipment_id": "SHP-2026-000002" }'
```

A Shipment without a tracking number and carrier is an **Open Shipment** — a standalone record you can enrich later, or hand to the Booking API to book with a carrier (which assigns the tracking number for you).

<Note>
  `shipment_id` must be unique across your account and cannot be changed after creation. Use a stable identifier from your own system, such as your OMS shipment ID.
</Note>

## Enable tracking

Add `tracking_number` and `carrier_reference` so Perform.AI can retrieve events from the carrier:

```json theme={null}
{
  "shipment_id": "SHP-2026-000002",
  "tracking_number": "00340434616780000001",
  "carrier_reference": "dhl-ecommerce-de"
}
```

Rules that matter here:

* **`carrier_reference` must be configured** in **Settings > Carriers** — an unconfigured value rejects the whole request (`4030`: "carrier\_reference specified in request has not been created.").
* `tracking_number` needs at least 6 characters including one digit; allowed characters are `A–Z a–z 0–9 _ - . /`, no spaces. It is stored uppercase.
* Once the Shipment has track events, `tracking_number` and `carrier_reference` can no longer be changed — get them right at creation.

## Recommended enrichment

These fields aren't required but power search, notifications, and analytics:

| Field                                                                | Why it matters                                                                                                                      |
| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `to_address.postal_code`, `to_address`/`from_address` city + country | Drive EDD prediction, domestic/international classification, and issue detection.                                                   |
| `tags`                                                               | Fast filtering in Parcel Overview and the List Shipments API. Stored lowercase; standardize your vocabulary.                        |
| `order_id`                                                           | Groups shipments into orders; creates the order if it doesn't exist.                                                                |
| `notification_email` / `notification_phone`                          | Subscribe recipients to tracking notifications. **Only these arrays notify** — the `email`/`phone` inside address objects never do. |
| `tracking_page_reference`                                            | Routes notifications and tracking links through your branded tracking page. Unconfigured values return a `299` warning.             |
| `line_items`, `shipping_costs`, `documents`                          | Enrich the record for returns, analytics, and the Parcel Details view.                                                              |

<Tip>
  Keep address lines short even though the API accepts up to 150 characters — carriers commonly enforce \~35 characters per line at booking, and long lines fail there, not here.
</Tip>

## Read the response

Success returns the identifiers Perform.AI assigned, flat under `data`:

```json theme={null}
{
  "api_response": "200",
  "data": {
    "shipment_uuid": "5b8ed503-20d2-46cf-8675-ce77a059f691",
    "order_uuid": "a0144751-6530-4135-8d73-f258a28ba27d",
    "documents": [],
    "created_date": "2026-09-19T03:26:29+00:00"
  }
}
```

Store `shipment_uuid` — it identifies the Shipment in every other endpoint.

**HTTP 299** means the Shipment was created but part of the request was skipped (an unconfigured tracking page, events without a carrier). Inspect `warnings` and fix with an [update](/guides/update-shipment) — don't resend the create, that attempts a duplicate. **HTTP 400** (`4030`) means nothing was created; fix the listed fields and resend. See [responses and errors](/essentials/responses-and-errors).

## Common validation failures

| Error message                                                  | Cause                                                               |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| `Duplicated.` (on `shipment_id`)                               | The ID already exists in your account.                              |
| `carrier_reference specified in request has not been created.` | The value isn't configured under **Settings > Carriers**.           |
| `Cannot define a new order_reference for existing order_id.`   | `order_reference` can only be set together with a *new* `order_id`. |
| `All shipping costs entries must be in the same currency.`     | Mixed currencies in `shipping_costs`.                               |
| `Either description or standard_key is required.`              | An entry in `events` has neither.                                   |
| `Country or country_code is required.`                         | An address object has neither.                                      |

## Next steps

* [Update the Shipment](/guides/update-shipment) — replace-vs-add semantics matter.
* [Retrieve its details](/guides/retrieve-shipment) and watch `all_events` fill.
* [Receive webhooks](/guides/webhooks) instead of polling.
