---
title: "Bulk Create or Update Orders"
method: POST
path: "/orders"
tags: ["Orders"]
---

# Bulk Create or Update Orders

`POST /orders`

## Request

- Base URL: `https://api.smartroutes.io/v2`
- URL: `https://api.smartroutes.io/v2/orders`
- Auth: none declared

Create or update up to 100 orders in your depot in one request. Orders are matched on `order_number`. A new number creates an order. A number that already exists in your depot updates that order instead of creating a duplicate, and restores it if it was deleted. Send each order complete, because some fields you leave out are reset rather than kept. To change only some fields of an existing order, use `PUT /orders/{id}`.

The whole batch is checked before anything is saved, and any problem rejects all of it with `400 INVALID_INPUT`. This includes:
- more than 100 orders, or the same `order_number` twice in one request
- an order with no address for its `type` (`shipment` needs both)
- a skill, vehicle or third party that doesn't exist
- an `order_exchange.reference` that isn't shared by exactly two orders

If your depot doesn't allow orders without customers, any order without a `customer` is rejected with `403 PERMISSION`. Custom fields, capacities and tags that don't exist in your depot are skipped silently, not rejected.

Addresses sent without coordinates are geocoded. A geocoding failure doesn't fail the request. The order is still saved, and the failure is listed in `errors` with name `GEOCODING_FAILED` and the order's `order_number`. The response gives the `id` and `order_number` of every order you sent. Fetch any of them in full with `GET /orders/{id}`. New orders start as `OPEN`, or as your depot's configured starting status. Existing orders keep their current status.

Supports [Idempotency](#section/Getting-started/Idempotency): retrying with the same `idempotency-key` returns the original response without processing the orders again.

## Headers

- `idempotency-key` string

## Request body

- object[]
  - `order_number` string, required: Order number.
  - `customer` union: Optional. When omitted, the order is created without a customer association (`customer_id` will be `null`). Requires the 'Orders Without Customers' depot setting to be enabled.
    - object
      - `id` number, required: ID of the customer.
      - `account` string: Account number of the customer, if both ID and account are provided, customer's account number will be updated if different to the customer's current account number.
      - `name` string: Name of the customer.
      - `email` string, email: Email of the customer.
      - `phone` string: Contact number of the customer.
      - `country_code` string: Country code of the customer.
      - `address` string: Address of the customer.
      - `postcode` string: Postcode of the customer.
      - `lat` number: Latitude of the customer location.
      - `lng` number: Longitude of the customer location.
    - object
      - `id` number: ID of the customer.
      - `account` string, required: Account number of the customer, if both ID and account are provided, customer's account number will be updated if different to the customer's current account number.
      - `name` string: Name of the customer.
      - `email` string, email: Email of the customer.
      - `phone` string: Contact number of the customer.
      - `country_code` string: Country code of the customer.
      - `address` string: Address of the customer.
      - `postcode` string: Postcode of the customer.
      - `lat` number: Latitude of the customer location.
      - `lng` number: Longitude of the customer location.
  - `type` 'delivery' | 'pickup' | 'shipment', required: Type of the order (delivery, pickup, or shipment).
  - `priority` string: Priority of the order (Accepts 'P1', 'P2', 'P3').
  - `delivery_contact_name` string: Name of the contact person.
  - `delivery_contact_number` string: Contact number of the person.
  - `delivery_contact_email` string, email: Email of the contact person.
  - `delivery_address` string: Delivery address.
  - `delivery_country_code` string: Two letter country code, e.g. 'IE'
  - `delivery_postcode` string: Postcode for delivery address.
  - `delivery_lat` number: Latitude of the delivery location.
  - `delivery_lng` number: Longitude of the delivery location.
  - `delivery_duration` number: Duration for order delivery in minutes.
  - `delivery_notes` string: Notes for delivery instructions.
  - `delivery_time_windows` object[]: List of time windows for order delivery.
    - `from` string, time, required: Start time of the time window.
    - `to` string, time, required: End time of the time window.
  - `delivery_date` string, date: Date for order delivery.
  - `delivery_available_days` string[]: List of available delivery days for the order.
  - `pickup_address` string: Address for order pickup.
  - `pickup_country_code` string: Two letter country code, e.g. 'IE'
  - `pickup_postcode` string: Postcode for pickup address.
  - `pickup_duration` number: Duration for order pickup in minutes.
  - `pickup_lat` number: Latitude of the pickup location.
  - `pickup_lng` number: Longitude of the pickup location.
  - `pickup_notes` string: Notes for pickup instructions.
  - `pickup_time_windows` object[]: List of time windows for order pickup.
    - `from` string, time, required: Start time of the time window.
    - `to` string, time, required: End time of the time window.
  - `pickup_contact_name` string: Name of the contact person for pickup.
  - `pickup_contact_number` string: Contact number of the person for pickup.
  - `pickup_contact_email` string, email: Email of the contact person for pickup.
  - `pickup_available_days` string[]: List of available pickup days for the order.
  - `order_exchange` object: Optional. Links this order to its partner as an exchange — a drop and a collect at one location served by the same vehicle in one consecutive visit. Provide the SAME reference on exactly two orders in the same request; the server pairs them and mints an internal exchange_id. A pair cannot be formed via single-order update.
    - `reference` string, required: Pairing key within this request — exactly two orders must share it. Display-only thereafter (never a join key).
  - `parts` number: Number of parts in the order.
  - `vehicle_assignment` object
    - `vehicle_id` integer: ID of the assigned vehicle
  - `emails` object[]
    - `email` string, email: Additional emails
  - `third_party` object: Third party object
    - `id` integer: ID of the third party for the order
  - `order_value` number, double, nullable: Total monetary value of the order, to 2 decimal places, at most 10,000,000. Settable **only** on an order with no valued line items — if any line item has a value, from `product_unit_price` or `product_value`, whether sent in this request or already stored, the value is derived from the lines and a non-null `order_value` returns `400`. Omit to keep what is stored; `null` clears a manual value (and is accepted, with no effect, on a derived one); `0` means a genuinely zero-value order. When sending back an order read from `GET /orders/{id}`, remove `order_value` if its `value_source` is `DERIVED` — echoing a derived value returns `400`. Orders are valued in the currency set on your organisation.
  - `line_items` object[]: List of line items in the order.
    - `product_code` string, required: Code of the product.
    - `product_name` string, required: Name of the product.
    - `product_quantity` number, float, required: Quantity of the product. May be fractional.
    - `product_unit_price` number, double, nullable: Price per unit, in your organisation's currency — not the line's value, which is `product_value`. Up to 4 decimal places, at most 1,000,000. Makes the line `DERIVED`: its value is `product_unit_price` × `product_quantity` rounded to 2 decimal places, recalculated when the quantity changes. Omit to keep the line's stored value; `null` clears it. Cannot be sent non-null together with `product_value`. Any valued line item makes `order_value` derived and therefore unsettable.
    - `product_value` number, double, nullable: Flat value of the line, in your organisation's currency, ignoring `product_quantity`. Up to 2 decimal places, at most 10,000,000. Makes the line `MANUAL` and clears any unit price. Returned on `GET /orders/{id}` as the line's value whatever its source — remove it before sending back a `DERIVED` line, which also carries `product_unit_price`, or the request returns `400`. Omit to keep the line's stored value; `null` clears it. Cannot be sent non-null together with `product_unit_price`; sending `product_unit_price: null` alongside it is accepted and switches the line to `MANUAL`.
    - `capacities` object[]: List of capacities for the line item.
      - `id` integer, required: Id of capacity (e.g., 1, 2).
      - `capacity` number, float, required: Capacity value for the specified type.
    - `date` string, date: Date for the line item.
    - `custom_fields` object[]: List custom fields for the line item
      - `id` integer: Id of custom field (e.g., 1, 2).
      - `value` string: New custom field value
  - `skills` string[]: List of required skills for the order.
  - `custom_fields` object[]: List of custom fields for the order.
    - `id` number: ID of the custom field.
    - `name` string: Name of the custom field.
    - `value` string, required: Value of the custom field.
  - `capacities` object[]: List of capacities for the order. Either id or type is required.
    - `id` integer: ID of a capacity.
    - `type` string: Type of the capacity
    - `capacity` number, float, required: Capacity value for the specified type.
  - `tags` string[]: List of tag names (must already exist)

## Response `200`

Successful response

## Other responses

- `400`: Invalid input. The request body failed validation.
- `403`: Forbidden. Returned for rate-limit exhaustion (`name = FORBIDDEN`, with `details.retry_after_seconds` and `Retry-After` header) and permission gate failures (`name = PERMISSION`). Inspect `name` to disambiguate. Send `x-sr-api-features: rate-limit-429` to receive `429 RATE_LIMIT_EXCEEDED` for the rate-limit case instead, leaving `403` to mean permission failures only — see [API Features](#section/Getting-started/API-Features).
- `404`: The requested resource was not found.
- `409`: A request with this idempotency key is already in progress. Retry after the period given in the `Retry-After` header.
- `422`: The same idempotency key was used with a different request body for this endpoint.
- `429`: Rate limit exceeded (`name = RATE_LIMIT_EXCEEDED`). **Only returned to callers that opt in** with the `x-sr-api-features: rate-limit-429` request header; without it the same condition returns `403` with `name = FORBIDDEN` and an otherwise identical body. See [API Features](#section/Getting-started/API-Features).
- `500`: An unexpected server error occurred. The body carries only `name` and `description`: internal details are never returned.

---

[API](https://skmtc.dev/smartroutes/apis/smartroutes-api.md) · [All operations](https://skmtc.dev/smartroutes/apis/smartroutes-api/llms.txt) · [OpenAPI document](https://skmtc.dev/smartroutes/apis/smartroutes-api/revisions/a7ea507afac7?raw)
