# Common Workflows

End-to-end flows that chain V5 endpoints together. Each workflow lists the
exact endpoint sequence and what to do with the response between steps.

## Courier shipment lifecycle

Create, rate, label, and ship a single courier package. The most common V5
flow — covers ~80% of carrier integrations.

| # | Step | Endpoint | What you do |
|  --- | --- | --- | --- |
| 1 | Create the shipment | `POST /shipments` with `type: courier` | Send `to_address` + `packages`. Returns `data.id` (the V5 `shipment_id`). |
| 2 | List rates | `GET /rates/{shipment_id}` | Pick a `carrier.service_code` (e.g. `canada_post.expedited`). |
| 3 | Buy the label | `POST /labels/{shipment_id}` with the chosen `service` | Charges credits. Returns `data.label_url` (PDF) + `data.tracking_code`. |
| 4 | Schedule a pickup (optional) | `POST /pickups` with `{ shipment_ids: [...] }` | Only valid for courier shipments (FedEx/UPS/Purolator via Freightcom). |


Use an `Idempotency-Key` header on steps 1, 3, and 4 so network retries can't
double-charge or double-book.

## LTL shipment booking

Less-than-truckload freight follows the same shape, just with `type: ltl` and
a different cancel endpoint.

| # | Step | Endpoint | What you do |
|  --- | --- | --- | --- |
| 1 | Create the LTL shipment | `POST /shipments` with `type: ltl` | Returns `data.id`. |
| 2 | Get LTL quotes | `GET /rates/{shipment_id}` | LTL carriers (Freightcom). |
| 3 | Book with the carrier | `POST /labels/{shipment_id}` with the chosen rate | Returns `data.confirmation_number`. |
| 4 | Cancel a booked LTL (optional) | `POST /ltl/{shipment_id}/cancel` | Voids the booking with the carrier. Use instead of `DELETE /shipments/{id}` for LTL. |


## Batch processing

Group many shipments into a single batch, generate labels for all of them, and
drain per-shipment failures.

| # | Step | Endpoint | What you do |
|  --- | --- | --- | --- |
| 1 | Create the batch | `POST /batches` | Send `{ name, shipment_ids }`. Returns `data.id`. |
| 2 | Trigger processing | `POST /batches/{batch_id}/process` | Returns `202`. Label generation runs async. |
| 3 | Poll status | `GET /batches/{batch_id}` | Repeat until `data.status != "processing"`. Realistic clients back off (1s → 2s → 4s). |
| 4 | Drain errors | `GET /batches/{batch_id}/errors` | Per-shipment failures with the validation problem. |


## Top up credits, then buy a label

Labels are paid from the account's credit balance. If the balance is low,
top up first.

| # | Step | Endpoint | What you do |
|  --- | --- | --- | --- |
| 1 | Charge the payment method | `POST /credits/top-up` with `{ amount, payment_method_id? }` | `payment_method_id` is optional — defaults to the account's default card. Returns `data.id` (the new transaction). |
| 2 | Confirm the new balance | `GET /credits/balance` | Sanity-check that the top-up landed and covers the upcoming label cost. |
| 3 | Buy the label | `POST /labels/{shipment_id}` with the chosen `service` | Debits the credit balance. |


**Always send an `Idempotency-Key` header on step 1.** Top-up retries without
a key double-charge.

## Classify and approve a product

Customs declarations need a per-product HS code. V5 wraps the TRU
classification API so you can ask for a suggested code, optionally verify the
manufacturer, then persist the product.

| # | Step | Endpoint | What you do |
|  --- | --- | --- | --- |
| 1 | Suggest an HS code | `POST /products/classify` with `{ title, description }` | Returns `data.hs_code`. |
| 2 | Verify the manufacturer (optional) | `POST /products/verify-manufacturer` with `{ manufacturer, country_of_origin }` | CUSMA / country-of-origin compliance check. |
| 3 | Create the product | `POST /products` with the verified `hs_code` and source fields | Persists the row that store imports and later shipment-creation calls reference. |


If an existing product enters `Manufacturer Failed` after import, you can run
just step 2 on it and re-call `POST /products/{sku}/approve-classification`.

## Other endpoint-level flows

These don't need a multi-step description — they're single calls or small
glue logic.

- **Quote rates without creating a shipment** — `POST /rates` with shipment
details. Returns rates. No shipment row is created.
- **Validate addresses** — `POST /addresses/validate` before `POST /shipments`
to surface bad zip/postal codes early.
- **Manage imported orders** — `POST /orders`, `GET /orders`,
`PUT /orders/{order_id}`. Delete only before linking to a non-voided
shipment.
- **Find drop-off locations** — `GET /locations`. Render the returned branch
and partner-site addresses; refresh periodically because the catalog
changes.


## Idempotency & retry rules

All mutating endpoints (`POST`, `PUT`, `DELETE`) accept an `Idempotency-Key`
header. A retried request with the same key inside 24h returns the original
response byte-for-byte. Reusing a key with a *different* body yields `409 idempotency_conflict`. See [Errors & Idempotency](/errors-idempotency).