Create a new shipment and optionally buy a label in one call.
Required authorization scope: `public.shipment:write` (and `public.label:write` if buying a label).
**Before calling:** collect complete address details from the user — contact names, emails, phones, and company name (required for origin).
**Parcel line items (server-enforced):** For every object in `parcels[].items[]`, the **user** must provide at least one of **`category`** (Easyship category slug) or **`hs_code`**. The MCP rejects the request when both (hs_code and category) are missing / empty is missing on any item — **do not pick a default** (e.g. do not assume `fashion`). Ask which product category applies or for the HS code.
## When to buy a label vs just create a shipment
- **"Ship this" / "Buy a label" / "Create a shipping label"** → Set `buy_label: true` and `buy_label_synchronous: true` in `shipping_settings`. Always use `format: "url"` in `printing_options` so the response contains a persistent clickable link.
- **"Create a shipment, don't buy the label yet"** → Omit `buy_label` or set it to `false`. The shipment is created in draft state; the user can buy the label later.
## Choosing a courier
- **No courier specified** → Omit `courier_settings.courier_service_id`. The API auto-selects the best value-for-money courier. **DO NOT call `get_rates` first.**
- **User names a specific courier** (e.g. "buy a label with UPS Ground") → Call `get_rates` first to find the matching `courier_id`, then pass it as `courier_settings.courier_service_id` with `allow_fallback: false`.
## Reading the label from the response
When `buy_label: true` and `buy_label_synchronous: true`, the response includes a `shipping_documents` array. Find the entry with `"category": "label"` and return its `url` field as a clickable Markdown link to the user. Example response structure:
```json
"shipping_documents": [
{ "category": "label", "format": "url", "url": "https://...", "page_size": "4x6" },
{ "category": "packing_slip", "format": "url", "url": "https://...", "page_size": "4x6" },
{ "category": "commercial_invoice", ... }
]
```
Always present the label URL to the user. If a `packing_slip` or `commercial_invoice` URL is also present, include those too.
## Payload schema
`shipment_data` is a JSON dict with this structure (fields marked * are required):
```json
{
"destination_address": {
"contact_name": "string *",
"contact_email": "string *",
"contact_phone": "string * (nullable)",
"line_1": "string * (max 35)",
"city": "string *",
"country_alpha2": "string * (ISO 3166-1)",
"state": "string (required for AU,CA,CN,ID,MX,MY,TH,US,VN)",
"postal_code": "string (required for most countries, null for HK)",
"line_2": "string",
"company_name": "string"
},
"origin_address": {
"company_name": "string * (max 27)",
"contact_name": "string * (max 22)",
"contact_email": "string *",
"contact_phone": "string *",
"line_1": "string * (max 35)",
"city": "string *",
"state": "string (required for AU,CA,CN,ID,MX,MY,TH,US,VN)",
"postal_code": "string (required for most countries)",
"country_alpha2": "string (ISO 3166-1)"
},
"parcels": [
{
"total_actual_weight": 1.5,
"box": { "length": 30, "width": 20, "height": 15 },
"items": [
{
"description": "string * (max 200)",
"quantity": 1,
"actual_weight": 1.5,
"dimensions": { "length": 10, "width": 10, "height": 10 },
"declared_currency": "USD",
"declared_customs_value": 50.0,
"category": "<slug from user — required unless hs_code set>",
"sku": "string",
"hs_code": "<from user — required unless category set>",
"contains_battery_pi966": false,
"contains_battery_pi967": false,
"contains_liquids": false,
"origin_country_alpha2": "US"
}
]
}
],
"incoterms": "DDU or DDP",
"insurance": { "is_insured": false },
"courier_settings": {
"courier_service_id": "uuid (omit to auto-select best courier)",
"apply_shipping_rules": true,
"allow_fallback": false
},
"shipping_settings": {
"buy_label": true,
"buy_label_synchronous": true,
"units": { "weight": "kg | g | lb | oz", "dimensions": "cm | in" },
"printing_options": {
"format": "url",
"label": "4x6 | A4 | A5",
"commercial_invoice": "4x6 | A4",
"packing_slip": "4x6 | A4 | none"
}
},
"order_data": {
"platform_name": "string",
"platform_order_number": "string",
"buyer_notes": "string",
"seller_notes": "string"
},
"set_as_residential": true,
"metadata": {}
}
```
## Weight and dimensions — three ways
1. Provide `total_actual_weight` + `box` at parcel level.
2. Provide `actual_weight` + `dimensions` per item — total weight and box auto-calculated.
3. Provide `sku` per item — weight and dimensions taken from product catalog.
## Item categories (valid slugs)
mobile_phones, tablets, computers_laptops, cameras, accessory_no_battery, accessory_with_battery,
health_beauty, fashion, watches, home_appliances, home_decor, toys, sport_leisure, bags_luggages,
audio_video, documents, jewelry, dry_food_supplements, books_collectibles, pet_accessory
Args:
shipment_data: The full shipment payload as a JSON dict. See schema above.
At minimum: `destination_address`, `origin_address` (or `origin_address_id`), and `parcels` with at least one item.
Returns:
The created shipment object including selected courier, rates, and — if `buy_label` was true — `shipping_documents` array with label/packing slip/invoice URLs.