---
title: "Optimise Orders"
method: POST
path: "/plans/optimise/orders"
tags: ["Plans"]
---

# Optimise Orders

`POST /plans/optimise/orders`

## Request

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

Async optimisation. Returns 200 immediately with a Plan id and an optimisation job id. The optimisation runs in the background; poll `GET /plans/{id}` to check progress.

**Initial response:** `{ "optimisation": { "id": "<uuid>", "in_progress": true }, "plan": { "id": <integer>, "dispatched": false } }`

**Polling** — `GET /plans/{id}`:
- *Completed:* `plan.routes` is non-empty (or `plan.unserved` is non-empty if no routes are viable). Either signals the optimisation has finished.
- *Still running:* `plan.routes` and `plan.unserved` are both empty.
- *Failed:* there is no terminal failure signal on the Plan resource today. A failed job leaves the Plan in the same "empty arrays" state as a still-running job. Implement a client-side hard timeout — recommended **10 minutes** — after which the job should be considered abandoned.

**Recommended polling cadence:**
- First minute: every 2 seconds.
- After 1 minute: every 10 seconds, exponential backoff up to 30 seconds.
- Hard timeout: 10 minutes total.

**Webhook coupling:** the [`plans.created`](#tag/Webhooks/operation/plans.created) webhook fires immediately after the Plan is persisted — *before* the optimisation job has completed. A webhook delivery does not imply the optimisation finished. Consumers receiving `plans.created` should still poll `GET /plans/{id}` to learn the outcome.

**Concurrency limits per depot** (queue, not timeout): STANDARD 100, LONG 100, DIST 7, BOOKING_CHECK 1. No documented per-job timeout; typical jobs complete in 5–30 seconds.

**Idempotency:** Supports the `idempotency-key` request header for safe retries. See the Idempotency section in the API description.

## Headers

- `idempotency-key` string

## Request body

- object
  - `delivery_date` string, date, required: The delivery date YYYY-MM-DD for optimizing orders.
  - `plan` object
    - `id` integer: You can provide a plan id to re-use for generating your route
    - `name` string: Provide a name for the plan. This will override the existing plan name if an id is provided.
  - `settings` object, required
    - `active_vehicles` boolean, required: Flag to include only active vehicles.
    - `auto_dispatch` boolean, required: Flag for auto_dispatch.
  - `vehicles` object[], nullable
    - `id` integer, required: ID of the vehicle.
    - `name` string: Name of the vehicle.
    - `availability` 'shift' | 'full_day' | 'always', required: Availability status of the vehicle.
    - `shift_start` string, time, nullable: Start time of the vehicle's shift (if availability is 'shift').
    - `shift_end` string, time, nullable: End time of the vehicle's shift (if availability is 'shift').
    - `start_location` 'depot' | 'other' | 'app-location', required: Location type where the vehicle starts.
    - `start_address` string, nullable: Address where the vehicle starts (if location is 'other').
    - `start_lng` number, nullable: Longitude of the starting location (if location is 'other').
    - `start_lat` number, nullable: Latitude of the starting location (if location is 'other').
    - `end_location` 'depot' | 'other' | 'none', required: Location type where the vehicle ends.
    - `end_address` string, nullable: Address where the vehicle ends (if location is 'other').
    - `end_lng` number, nullable: Longitude of the ending location (if location is 'other').
    - `end_lat` number, nullable: Latitude of the ending location (if location is 'other').
    - `break` boolean: Flag indicating whether the vehicle has a break.
    - `break_start` string, time, nullable: Start time of the vehicle's break (if it has a break).
    - `break_end` string, time, nullable: End time of the vehicle's break (if it has a break).
    - `break_duration` number, nullable: Duration of the vehicle's break in minutes (if it has a break).
    - `active` boolean: Flag indicating whether the vehicle is active.
    - `skills` string[]: Array of skills associated with the vehicle.
    - `capacities` object[]: Array of capacities associated with the vehicle.
      - `id` integer, required: Id of capacity (e.g., 1, 2).
      - `capacity` number, float, required: Capacity value for the specified type.
  - `zone_group_id` integer: The ID of the zone group for optimization.

## Response `200`

Optimisation accepted and Plan created. The optimisation runs asynchronously; poll `GET /plans/{id}` for completion.

- PlanOptimisationResponse
  - `optimisation` object, required
    - `id` string, uuid, required
    - `in_progress` boolean, required
  - `plan` object, required
    - `id` integer, required
    - `dispatched` boolean, required
  - `errors` object[]: Orders left out of the optimisation, each with the reason. May be absent.
    - `name` string, required: Reason code (e.g. `IN_ANOTHER_PLAN`).
    - `description` string, required: Human-readable explanation.
    - `details` object, required
      - `id` integer, required: Order id.
      - `order_number` string, nullable, required: Order number.
      - `plan_ids` integer[]: Plans the order already belongs to.

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