---
title: "Add Orders to a Route"
method: POST
path: "/routes/{id}/add-orders"
tags: ["Routes"]
---

# Add Orders to a Route

`POST /routes/{id}/add-orders`

## Request

- Base URL: `https://api.smartroutes.io/v2`
- URL: `https://api.smartroutes.io/v2/routes/{id}/add-orders`
- Auth: none declared

Bulk-add existing unrouted orders to a single route, without re-running a full plan optimisation. The orders must already exist — this endpoint does not create them. This is a partial-success bulk operation returning `{ orders, errors }` (the same envelope as `POST /orders` and `POST /routes/{id}/remove-orders`): one un-addable order never fails the rest of the batch.

The route id is given in the path (`/routes/{id}/add-orders`) and scopes the whole call. Each order is identified by `id` (depot-scoped, preferred) and/or `order_number` — at least one is required per entry; if both are given they must resolve to the same order.

**Placement.** By default each order is APPENDED to the end of the route, as a new stop before the end stop, in the order given in the request. Nothing else on the route moves and no solve runs, so the call is fast and predictable. Two consequences: appending does NOT check feasibility (vehicle capacity, driver skills and time windows are not validated — the stop is placed because you asked for it), and a route can end up with two stops at the same address, because an added order is always a new stop and is never merged into a stop the route already has there. Callers who need a feasible, sensibly-sequenced route use `settings.reoptimize`. There is no per-order position field in this version.

**Re-optimising.** Set `settings.reoptimize` to `true` to re-solve the whole route so the new stops are placed by the solver rather than appended. This runs synchronously — the request blocks until the route has been solved and applied, so it can take seconds to minutes. It also re-sequences stops you never mentioned, and the solver may decline to place an order it cannot serve, which is reported as `NOT_PLACED` for that order. A route whose existing stops plus the requested additions exceed 120 is reported as `REOPTIMIZE_LIMIT` and no solve is attempted.

`orders` and `errors` are NOT mutually exclusive. `orders` lists every order that resolved to a real record, each with the `stops` it was added to (`{ id, route: { id } }`) — an empty `stops` array means it was not added. `errors` is a parallel list of problems; an order that exists but could not be added appears in BOTH `orders` (with empty `stops`) and `errors`. An order reference that does not resolve at all (`ORDER_NOT_FOUND`) appears only in `errors`. A response never reports an add that is not backed by a real stop: what landed is confirmed against the route after the change is applied.

Each `errors` entry is a `{ name, description, details }` envelope where `name` is the failure reason and `details` carries at least the `id` / `order_number`. Failure `name`s are `ORDER_NOT_FOUND`, `ID_NUMBER_MISMATCH`, `ROUTE_DISPATCHED`, `ALREADY_ROUTED`, `PREVIOUSLY_ON_ROUTE`, `ORDER_COMPLETED`, `ORDER_TYPE_UNSUPPORTED`, `STOP_BUILD_FAILED`, `NOT_PLACED`, `REOPTIMIZE_LIMIT` and `ADD_FAILED`. An order already on an active route is reported as `ALREADY_ROUTED` and is never moved implicitly — remove it from that route first, then add it to the one you want. Orders that have already been delivered or marked undelivered (in full or in part) are reported as `ORDER_COMPLETED`. Shipment orders (a pickup and delivery pair) and exchange-linked orders are not supported and are reported as `ORDER_TYPE_UNSUPPORTED`.

**Only orders new to the route can be added.** An order that has been on this route before — added and then removed again — is reported as `PREVIOUSLY_ON_ROUTE` and is not re-added. This holds on both the append and the `reoptimize` path, and it applies to the route's whole history, not just its current stops. To get a removed order back onto the same route, re-run a plan optimisation (`POST /plans/optimise/orders-for-ids`) rather than adding it; to move an order between routes, remove it from the first and add it to the second, which is a different route and so unaffected.

By default adding to a dispatched (live) route is refused and every order is reported as `ROUTE_DISPATCHED`; set `settings.allow_dispatched` to `true` to edit live routes. Adding a stop to a route a driver is already running changes their live workload, so it is opt-in per call: when allowed, the customer is notified for the new stop, the driver's app is pushed the updated route, and the change is written to the route audit log.

## Path parameters

- `id` integer, required

## Request body

- object
  - `orders` object[], required: 1..10 order references to add. Appended to the route in this order.
    - `id` integer: Order id (depot-scoped). Preferred when present.
    - `order_number` string: Order number. Used when `id` is absent.
  - `settings` object
    - `allow_dispatched` boolean: When false (default) adding to a dispatched (live) route is refused and every order is reported as `ROUTE_DISPATCHED`. When true, the live route is edited: the customer is notified for the new stop, the driver's app is pushed the updated route, and the route audit log is written.
    - `reoptimize` boolean: When false (default) each order is appended to the end of the route and nothing else moves; feasibility (capacity, skills, time windows) is not checked. When true the whole route is re-solved so the new stops are placed by the solver — this runs synchronously (the request blocks for the full solve and apply, seconds to minutes), re-sequences stops you did not mention, and may decline to place an order (reported as `NOT_PLACED`). A route whose stops plus additions exceed 120 is reported as `REOPTIMIZE_LIMIT` with no solve attempted.

## Response `200`

Bulk result. `orders` lists every resolved order (with the stops it was added to); `errors` is a parallel list of problems and may reference an order also present in `orders`. Returned for fully-successful and mixed batches alike.

- object
  - `orders` object[]: Every order that resolved to a real record. An empty `stops` array means it was not added (look it up in `errors` for the reason).
    - `id` integer: Order id.
    - `order_number` string: Order number.
    - `stops` object[]: The stop(s) the order was added to; empty when it was not added.
      - `id` integer: Stop (visit) id the order was added to.
      - `route` object
        - `id` integer: Route id the stop belongs to.
  - `errors` object[]: Orders that could not be added.
    - `name` string: Failure reason code (e.g. `ALREADY_ROUTED`, `ROUTE_DISPATCHED`).
    - `description` string: Human-readable explanation.
    - `details` object: At least `id` and `order_number`; may carry extra context (e.g. `route_id` — for `ALREADY_ROUTED` this is the route the order is CURRENTLY on, not the route you were adding to).
      - `id` integer, nullable
      - `order_number` string, nullable

## 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.
- `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)
