# Uniorder: one API for every shipment

Uniorder is a single set of endpoints through which an integration quotes, creates, prints, tracks and cancels every shipment of the account, whichever way the shipment is fulfilled. One quote returns delivery by the business itself and, on request, every carrier label service of the account, each with a `rate_id`. The order is created by returning the chosen `rate_id`; nothing else in the request selects the service.

## 1. What you can build

- **A checkout that offers every shipping option at once.** The customer enters an address, the checkout calls one endpoint, and the page lists local delivery beside UPS, Canada Post and any other carrier the account uses, each with its price.
- **An order-management or ERP connector with one code path.** Orders from every channel go through the same create, read, label, tracking and cancel calls. The connector does not need separate logic for local delivery and for carrier labels.
- **Overnight bulk processing.** Up to 500 shipments are quoted or created in one queued job, and the results are read back by job id.
- **A customer-service screen.** An agent looks up an order, reprints its label, reads its tracking timeline and cancels it, with the same four calls for every order.

## 2. What Uniorder does for you

| Without Uniorder | With Uniorder |
|---|---|
| One API for local delivery orders and another for carrier labels, each with its own fields and responses | One request shape (`from_*`, `to_*`, `packages`) and one response shape for every service |
| The integration decides which carrier API to call | The quote lists every service; the `rate_id` of the chosen rate decides |
| Separate label, tracking and cancel endpoints per service | `GET /label`, `GET /tracking` and `POST /cancel` work for every order |
| Batch creation available for local delivery only | Batch quote and batch create for every service, synchronous or queued |

Uniorder does not replace the existing endpoints; they remain available and unchanged. It is the recommended entry point for a new integration.

## 3. Endpoint map, in the order an integration uses them

| Step | Purpose | REST | GraphQL |
|---|---|---|---|
| 1 | Obtain an access token | `POST /api/v1/user/login` | `userLogin` |
| 2 | Quote every service | `POST /api/v1/uniorder/rate` | `uniorderRate` |
| 3 | Create the order at the chosen rate | `POST /api/v1/uniorder` | `uniorderCreate` |
| 4 | Print the label | `GET /api/v1/uniorder/{orderId}/label` | `uniorderLabel` |
| 5 | Read the order | `GET /api/v1/uniorder/{orderId}` | `uniorder` |
| 6 | Track the order | `GET /api/v1/uniorder/{orderId}/tracking` | `uniorderTracking` |
| 7 | Cancel the order | `POST /api/v1/uniorder/{orderId}/cancel` | `uniorderCancel` |
| — | Buy a label that could not be bought at creation | `POST /api/v1/uniorder/{orderId}/label` | `uniorderPurchaseLabel` |
| — | Quote or create up to 20 rows at once | `POST /api/v1/uniorder/rate/batch`, `POST /api/v1/uniorder/batch` | `uniorderRateBatch`, `uniorderCreateBatch` |
| — | Queue up to 500 rows | `POST /api/v1/uniorder/rate/batch-async`, `POST /api/v1/uniorder/batch-async`, `GET /api/v1/uniorder/jobs/{jobId}` | `uniorderRateBatchAsync`, `uniorderCreateBatchAsync`, `uniorderJob` |

[REST handbook](/api/documentation#/paths/v1-uniorder-rate/post) · [GraphQL handbook](/api/graphql/documentation#/orders/uniorderRate)

The step-by-step requests, responses and checks are in the playbook **Quote and order in one flow**.

## 4. Example: a checkout that offers every option

A florist in Montreal sells online. At checkout it quotes the parcel once, with label carriers included:

```bash
curl -X POST https://YOUR_HOST/api/v1/uniorder/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from_name": "Fleurs du Plateau", "from_telephone": "5145550100",
    "from_address": "4500 Rue Saint-Denis", "from_city": "Montreal", "from_province": "QC", "from_country": "CA", "from_postcode": "H2J2L3",
    "to_name": "Jane Recipient", "to_telephone": "5145550199",
    "to_address": "6841 Rue Saint-Denis", "to_city": "Montreal", "to_province": "QC", "to_country": "CA", "to_postcode": "H2S2S3",
    "quote_labels": true,
    "packages": [{ "weight": 1.2, "weight_unit": 2, "length": 40, "width": 25, "height": 25, "dimension_unit": 2 }]
  }'
```

The answer lists one `self_delivery` rate and one `label_service` rate per carrier service. The checkout shows them as options; the customer chooses same-day local delivery. The order is created with that rate's `rate_id` and the same addresses and packages:

```bash
curl -X POST https://YOUR_HOST/api/v1/uniorder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: checkout-10045" \
  -d '{
    "rate_id": "eyJpdiI6Ik1rT2Z...",
    "ref": "WEB-10045",
    "from_name": "Fleurs du Plateau", "from_telephone": "5145550100",
    "from_address": "4500 Rue Saint-Denis", "from_city": "Montreal", "from_province": "QC", "from_country": "CA", "from_postcode": "H2J2L3",
    "to_name": "Jane Recipient", "to_telephone": "5145550199",
    "to_address": "6841 Rue Saint-Denis", "to_city": "Montreal", "to_province": "QC", "to_country": "CA", "to_postcode": "H2S2S3",
    "packages": [{ "weight": 1.2, "weight_unit": 2, "length": 40, "width": 25, "height": 25, "dimension_unit": 2 }]
  }'
```

The response returns the order `id` and its tracking numbers. The store prints the label with `GET /api/v1/uniorder/{orderId}/label` and shows the timeline from `GET /api/v1/uniorder/{orderId}/tracking` on the customer's order page.

## 5. Example: an ERP that ships every night

An ERP exports the day's orders at 22:00. It sends the addresses to `POST /api/v1/uniorder/rate/batch-async`, reads the job until `status` is `done`, selects a rate for each row according to its own rules, and sends the chosen rows to `POST /api/v1/uniorder/batch-async`. Each result carries the row's `reference`, so the ERP matches every result to its own order line. A `rate_id` is valid for 30 minutes, so the create job is sent soon after the quote job completes.

## 6. Example: a customer-service screen

For a customer call, the agent's screen calls `GET /api/v1/uniorder/{orderId}` for the status and addresses, `GET /api/v1/uniorder/{orderId}/tracking` for the timeline and proof of delivery, and `POST /api/v1/uniorder/{orderId}/cancel` when the customer cancels. The same calls apply to a delivery order and to a label order; the `type` field tells them apart.

```bash
curl https://YOUR_HOST/api/v1/uniorder/123456/tracking \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

The same order over GraphQL ([GraphQL handbook](/api/graphql/documentation#/orders/uniorder)):

```graphql
query {
  uniorder(order_id: 123456)
}
```

## 7. Rules to design around

- **The `rate_id` decides the service.** It is valid for 30 minutes and only for the account that requested the quote.
- **The order is priced when it is created.** `shipping_price` is the price charged; `quoted_price` is the price of the quote. The two can differ.
- **A label order is never lost.** When the label cannot be bought at creation, the order is kept and the answer is `LABEL_PURCHASE_FAILED` with the order `id`; the label is bought later with `POST /api/v1/uniorder/{orderId}/label`.
- **Retries are safe.** Send an `Idempotency-Key` header on every create call; a cancel of an order already cancelled returns `already_cancelled` `true`.
- **Statuses are uniform.** A delivery order reports `pending`, `in_transit`, `out_for_delivery`, `delivered`, `exception` or `cancelled`; a label order reports `label_pending`, `label_purchased` or `cancelled`, and its carrier's tracking reports where the parcel is.

## 8. Go-live checklist

- [ ] The account has API permission and the token is stored on the server, not in a browser.
- [ ] Quotes are requested with complete sender and recipient addresses.
- [ ] Orders are created within 30 minutes of the quote, with an `Idempotency-Key`.
- [ ] `LABEL_PURCHASE_FAILED` is handled by buying the label later, never by creating the order again.
- [ ] Tracking is read from `GET /api/v1/uniorder/{orderId}/tracking` or received through webhooks.
- [ ] Cancellation handles `ORDER_STATUS_NOT_CANCELLABLE` and `ORDER_CANCEL_REFUSED` by leaving the order as it is.
