Packing and labels
Each paid store order is split into fulfilments: one per location sending some of its lines. A fulfilment goes from the pick list to packed, gets a label, and is shipped. The carrier’s tracking then moves it to delivered.
1. Find what to pack
Section titled “1. Find what to pack”GET /v1/fulfilments is the pick list, oldest promise first, each line with its barcodes and
weight. Filter with status (comma separated; default allocated,picking,packed,label_purchased),
location, kind and limit (up to 100).
curl -sS "https://api.tuppence.ai/v1/fulfilments?status=allocated" \ -H "Authorization: Bearer $TUPPENCE_SECRET_KEY"2. Pack it
Section titled “2. Pack it”POST /v1/fulfilments/{id}/pack takes scans: a barcode packs one item, and a wrong one is
refused with 422 wrong_item, naming what it is and what the order wants. A line with no barcode
is ticked by hand ({"line": "fulln_…"}). Send package and the scales’ weight_g to set the
parcel. When everything is in, the fulfilment is packed.
GET /v1/fulfilments/{id}/packing_slip is the slip: the order number and its QR code, the address,
what is in the box (no prices) and how to return it.
3. Buy the label
Section titled “3. Buy the label”GET /v1/fulfilments/{id}/rates answers what a label costs for the packed parcel, by service,
beside expected_amount (the price at the weight your product data gave).
POST /v1/fulfilments/{id}/label buys it. It spends money, so it needs an Idempotency-Key, and a
parcel’s label is bought once: asking again answers the same shipment and charges nothing.
curl -sS -X POST "https://api.tuppence.ai/v1/fulfilments/ful_123/label" \ -H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \ -H "Idempotency-Key: label-ful_123" \ -H "Content-Type: application/json" \ -d '{}'| Error | Fix |
|---|---|
409 label_price_changed | The price is more than a fifth above expected_amount. Send it again with confirm_amount |
409 invalid_state | Pack the order first |
POST /v1/fulfilments/labels buys up to 50 at once; each result says bought,
needs_confirmation or failed.
GET /v1/shipments/{id}/label?format=pdf prints it: pdf (4x6), png, zpl or pdf_a4, or
?printer= a label printer. A label carries the shopper’s address, so it’s never cached.
POST /v1/fulfilments/{id}/label/void voids an unused label. The parcel goes back to packed, and
a label paid from your balance comes back once the carrier refunds it. Carriers whose labels can’t
be voided (DPD UK) are refused, saying so.
4. Mark it shipped
Section titled “4. Mark it shipped”POST /v1/fulfilments/{id}/ship marks it shipped. Posted without our label, send the receipt’s
tracking_number (and carrier) so its tracking moves the order on. The shopper is told it’s on
its way. Marking it again changes nothing.
Test mode
Section titled “Test mode”In test mode, labels come from the carriers’ test accounts, cost test money only, and say TEST - NOT FOR POSTING across them.
Agents and the assistant
Section titled “Agents and the assistant”| Tool | Does |
|---|---|
list_fulfilments, get_fulfilment | The pick list, and a parcel with the carrier’s label prices once packed |
get_shipment | A label and the carrier’s tracking events |
buy_label, void_label | Spend money: over MCP they need an idempotency_key; in the assistant a person approves each one |
mark_shipped | Marks it shipped, with its label or a tracking number |
Events
Section titled “Events”| Event | When |
|---|---|
fulfilment.shipped | It left |
fulfilment.delayed | It is late: not sent, no tracking news, or past its latest delivery day |
shipment.in_transit, shipment.out_for_delivery, shipment.delivered | The carrier’s tracking |
shipment.exception, shipment.returned_to_sender | Something went wrong in transit |
shipment.updated | Any change to the parcel |