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

# Orders

> Create, list, read and cancel delivery orders with the Vendor API.

A delivery order sends your items to a customer: by UrbnBox rider to their
address, or for the customer to collect. Start with the
[quickstart](https://urbnbox.mintlify.site/quickstart.md) to create your first order.

## Authentication

All Vendor API endpoints require an `X-API-Key` header with a vendor key.
Keys begin with `ubx_`. Create and revoke them under **Integrations** in the
vendor dashboard.

```http theme={null}
X-API-Key: ubx_your_vendor_key
```

## Create order

`POST /api/vendor/v1/orders`

```json theme={null}
{
  "external_reference": "SHOP-ORDER-1001",
  "client_name": "Jane Mwangi",
  "client_phone": "+254712345678",
  "fulfillment_type": "delivery",
  "address": "Kenyatta Avenue, Nairobi",
  "items": [
    { "product_id": "PRODUCT_ID", "quantity": 2 }
  ]
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `client_name` | string | Yes | Customer's full name |
| `client_phone` | string | Yes | Customer's WhatsApp number. UrbnBox contacts the customer on WhatsApp |
| `items` | array | Yes | At least one `{ "product_id", "quantity" }`. See [Items](#items) |
| `fulfillment_type` | string | No | `delivery` (default), `scheduled_delivery`, `self_pickup` or `scheduled_pickup` |
| `address` | string | For deliveries | Delivery address. Required for `delivery` and `scheduled_delivery` |
| `client_lat`, `client_lng` | number | No | Delivery coordinates. When omitted, UrbnBox geocodes `address` |
| `scheduled_at` | string | For scheduled types | RFC 3339 date-time, for example `2026-11-02T10:00:00+03:00` |
| `pickup_name`, `pickup_phone` | string | For pickups | Who collects the order. Required for `self_pickup` and `scheduled_pickup` |
| `external_reference` | string | No | Your order ID. See [Idempotency](#idempotency) |

### Fulfilment types

| Type | What happens | Also requires |
| - | - | - |
| `delivery` | A rider delivers to the customer's address once the order is confirmed | `address` |
| `scheduled_delivery` | A rider delivers at the scheduled time | `address`, `scheduled_at` |
| `self_pickup` | The customer collects the order | `pickup_name`, `pickup_phone` |
| `scheduled_pickup` | The customer collects at the scheduled time | `pickup_name`, `pickup_phone`, `scheduled_at` |

An unrecognised value is treated as `delivery`.

### Items

Each item references a product in your UrbnBox catalogue. List your products
with `GET /api/vendor/v1/products` to find their IDs. UrbnBox rejects the order
with `400` when a product:

* does not exist for your vendor account,
* is inactive, or
* does not have enough available stock for the requested `quantity`.

Creating an order reserves the stock for its items. The order total is the sum
of the item prices plus the delivery fee.

### Delivery fee

UrbnBox calculates `DeliveryFeeKES` when the order is created, using the
delivery coordinates. Send `client_lat` and `client_lng` when you have them.
If you send only `address` and it cannot be geocoded, the order is created
without coordinates; check `DeliveryFeeKES` in the response.

### Idempotency

When `external_reference` matches an order you already created, UrbnBox returns
that order instead of creating a duplicate. The response status is `201` in
both cases. Send a stable `external_reference` for every order so retries are
safe.

### Response

`201 Created` with the order:

```json theme={null}
{
  "ID": "4f9c2b1e-…",
  "Status": "draft",
  "FulfillmentType": "delivery",
  "ClientName": "Jane Mwangi",
  "ClientPhone": "+254712345678",
  "Address": "Kenyatta Avenue, Nairobi",
  "ClientLat": -1.2841,
  "ClientLng": 36.8233,
  "TotalKES": 3350,
  "DeliveryFeeKES": 350,
  "PublicToken": "o_…",
  "IsCOD": false,
  "CODAmountKES": null,
  "ScheduledAt": null,
  "CreatedAt": "2026-11-01T09:12:44Z",
  "UpdatedAt": "2026-11-01T09:12:44Z"
}
```

> [!NOTE]
>
> Order responses use these capitalised field names (`ID`, `Status`,
> `TotalKES`). Request fields are lowercase with underscores.

## Order status

| Status | Meaning |
| - | - |
| `draft` | Created. Waiting for the customer to confirm |
| `confirmed` | The customer confirmed the order |
| `assigned` | A rider is assigned |
| `packing` | The items are being prepared |
| `dispatched` | The rider is on the way |
| `delivered` | The order was delivered or collected |
| `cancelled` | The order was cancelled |

## List orders

`GET /api/vendor/v1/orders`

Returns your orders, newest first.

| Parameter | Type | Description |
| - | - | - |
| `page_size` | integer | `10`, `25` (default), `50` or `100` |
| `cursor` | string | The `next_cursor` from the previous response |

Use cursor pagination to walk through all orders:

```json theme={null}
{
  "data": [{ "ID": "4f9c2b1e-…", "Status": "dispatched" }],
  "page_size": 25,
  "next_cursor": "MjAyNi0xMS0wMVQwOToxMjo0NFo="
}
```

Request the next page with `?cursor=<next_cursor>`. When `next_cursor` is
empty, you have reached the last page.

## Get order

`GET /api/vendor/v1/orders/{id}`

Returns the order and its items:

```json theme={null}
{
  "order": { "ID": "4f9c2b1e-…", "Status": "assigned" },
  "items": [
    { "ProductID": "…", "ProductName": "Blue hoodie", "Quantity": 2, "UnitKES": 1500, "Status": "RESERVED" }
  ]
}
```

Returns `404` when the order does not exist for your vendor account.

## Cancel order

`POST /api/vendor/v1/orders/{id}/cancel`

Cancels the order and returns `{ "status": "cancelled" }`.

## Create orders in bulk

`POST /api/vendor/v1/orders/bulk`

Send a JSON array of orders, each with the same fields as
[Create order](#create-order). UrbnBox creates each order independently and
returns one result per array index:

```json theme={null}
{
  "results": [
    { "index": 0, "order": { "ID": "…", "Status": "draft" } },
    { "index": 1, "error": "invalid order input: insufficient stock for Blue hoodie (1 available)" }
  ]
}
```

> [!WARNING]
>
> The bulk endpoint does not read `external_reference`, so it does not
> deduplicate. Retrying a bulk request creates the orders again. Use
> `POST /api/vendor/v1/orders` for each order when you need safe retries.

## Check a CSV before you send it

Use the CSV tools to validate a spreadsheet of orders. They check the rows;
they do not create orders.

### Step 1: download the template

`GET /api/vendor/v1/orders/csv-template`

Returns `urbnbox-orders-template.csv` with the headers and two example rows.
No authentication required.

| # | Column | Notes |
| - | - | - |
| 1 | `external_reference` | Your order ID. Required |
| 2 | `client_name` | Customer's full name. Required |
| 3 | `client_phone` | Kenyan number (`07xx` or `+254xx`) |
| 4 | `description` | Item description |
| 5 | `quantity` | Positive integer |
| 6 | `unit_price_kes` | Positive price in KES, for example `1500` |
| 7 | `pickup_type` | `delivery` or `locker` |

### Step 2: upload for validation

`POST /api/vendor/v1/orders/import-csv`

Upload the file as `multipart/form-data` with the field name `file`. The
response lists every row with its parsed values and an `Error`, which is empty
for valid rows:

```json theme={null}
{
  "valid_count": 47,
  "error_count": 2,
  "total_count": 49,
  "rows": [
    { "RowNum": 1, "ExternalRef": "ORD-001", "ClientName": "Jane Doe", "Quantity": 2, "UnitPriceKES": 1500, "PickupType": "delivery", "Error": "" },
    { "RowNum": 12, "ExternalRef": "ORD-012", "ClientName": "", "Error": "client_name required" }
  ]
}
```

Fix the errors, then create the orders with
`POST /api/vendor/v1/orders/bulk` or `POST /api/vendor/v1/orders`. Those
endpoints take `items` with product IDs rather than the CSV's description and
price columns.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.