Route optimization
Plan routes across a fleet from jobs, vehicles and constraints you supply.
routing.optimize
Stable
node
How to call it
REST
POST
/v1/routing/optimize
202
GraphQL
mutation
openRoutingOptimize
MCP
open_routing_optimize
Plan routes across a fleet. Billed per node — every point the solver must visit or depart from, INCLUDING vehicle start and end. A large problem returns a job id instead of a plan; poll it with open_job_get and collect it with open_job_result. Optional routing rules: ride time limits, release times, order groups, incompatible goods classes, alternative locations, deliveries before pickups, waiting cost, use all vehicles, stop merging, reload depots and loading bays, EV range, shifts, and break skip policies and places. An engine that cannot plan a rule refuses it with VALIDATION_FAILED; an approximated rule comes back as a RULE_APPROXIMATED warning. Which engine plans which rule, from which engine version, is listed as routing_rules by open_routing_presets.
Request schema
Generated from the same validation rules the endpoint runs, so a field cannot be documented without being validated.
POST /v1/routing/optimize
{
"type": "object",
"properties": {
"callback": {
"type": [
"object",
"null"
],
"properties": {
"events": {
"type": "array",
"items": {
"type": "string",
"maxLength": 32
}
},
"format": {
"type": [
"string",
"null"
],
"enum": [
"native",
"ce_binary",
"ce_structured"
]
},
"secret": {
"type": [
"string",
"null"
],
"minLength": 16,
"maxLength": 255,
"description": "Required when callback.signature is standard_webhooks."
},
"signature": {
"type": [
"string",
"null"
],
"enum": [
"native",
"standard_webhooks"
]
},
"url": {
"type": "string",
"maxLength": 2048,
"format": "uri",
"description": "Required when callback is present."
}
}
},
"constraints": {
"type": [
"object",
"null"
],
"properties": {
"balance": {
"type": [
"string",
"null"
],
"enum": [
"none",
"balanced",
"min_change",
"sla_protect",
"tolerance",
"zone",
"hours",
"zone_hours"
]
},
"balance_tolerance": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 100
},
"deliveries_before_pickups": {
"type": [
"boolean",
"null"
]
},
"incompatible_goods_classes": {
"type": [
"array",
"null"
],
"items": {
"type": "array",
"items": {
"type": "string",
"maxLength": 64
}
},
"maxItems": 500
},
"max_ride_seconds": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 86400
},
"merge_stops": {
"type": [
"object",
"null"
],
"properties": {
"max_stops": {
"type": [
"integer",
"null"
],
"minimum": 2,
"maximum": 50
},
"radius_meters": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"description": "Required when constraints.merge_stops is present."
},
"same_window_only": {
"type": [
"boolean",
"null"
]
}
}
},
"region_filter": {
"type": [
"boolean",
"null"
]
},
"reload": {
"type": [
"object",
"null"
],
"properties": {
"depots": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"description": "Required when constraints.reload is present.",
"items": {
"type": "object",
"properties": {
"id": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"loading_bays": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"location": {
"type": "array"
}
}
}
},
"loading_seconds": {
"type": [
"integer",
"null"
],
"minimum": 0,
"maximum": 14400
},
"max_reloads": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 5
},
"slot_seconds": {
"type": [
"integer",
"null"
],
"minimum": 60,
"maximum": 14400
}
}
},
"use_all_vehicles": {
"type": [
"string",
"null"
],
"enum": [
"off",
"low",
"normal",
"high"
]
},
"waiting_cost": {
"type": [
"string",
"null"
],
"enum": [
"off",
"low",
"normal",
"high"
]
}
}
},
"engine": {
"type": [
"string",
"null"
],
"enum": [
"sroute",
"sroute2",
"sroute2t",
"sroute3"
]
},
"jobs": {
"type": "array",
"maxItems": 10000,
"items": {
"type": "object",
"properties": {
"alternatives": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"id": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"location": {
"type": "array"
},
"time_window": {
"type": [
"array",
"null"
]
}
}
},
"maxItems": 10
},
"amount": {
"type": [
"array",
"null"
]
},
"goods_class": {
"type": [
"string",
"null"
],
"maxLength": 64
},
"group": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"id": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"location": {
"type": "array",
"description": "Required when jobs is present."
},
"priority": {
"type": [
"integer",
"null"
],
"minimum": 0,
"maximum": 100
},
"release_at": [],
"service_seconds": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"setup_seconds": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"skills": {
"type": [
"array",
"null"
]
},
"time_windows": {
"type": [
"array",
"null"
]
}
}
}
},
"mode": {
"type": [
"string",
"null"
],
"enum": [
"auto",
"sync",
"async"
]
},
"options": {
"type": [
"object",
"null"
],
"properties": {
"geometry": {
"type": [
"boolean",
"null"
]
}
}
},
"profile": {
"type": [
"string",
"null"
],
"enum": [
"car",
"van",
"truck",
"bike",
"foot",
"motorcycle"
]
},
"region": {
"type": [
"string",
"null"
],
"enum": [
"NA",
"EU",
"AU",
"ASIA",
"na",
"eu",
"au",
"asia"
]
},
"shipments": {
"type": "array",
"maxItems": 5000,
"items": {
"type": "object",
"properties": {
"delivery": {
"type": "array",
"description": "Required when shipments is present."
},
"goods_class": {
"type": [
"string",
"null"
],
"maxLength": 64
},
"group": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"id": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"max_ride_seconds": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"pickup": {
"type": "array",
"description": "Required when shipments is present."
}
}
}
},
"vehicles": {
"type": "array",
"minItems": 1,
"maxItems": 2000,
"items": {
"type": "object",
"properties": {
"breaks": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"duration_seconds": [],
"places": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"location": {
"type": "array"
}
}
},
"maxItems": 10
},
"skip_policy": {
"type": [
"string",
"null"
],
"enum": [
"if_no_intersection",
"if_route_ends_before_window_end"
]
},
"windows": []
}
},
"maxItems": 10
},
"capacity": {
"type": [
"array",
"null"
]
},
"end": {
"type": [
"array",
"null"
]
},
"ev_range_meters": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"ev_reserve_meters": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"id": {
"type": [
"string",
"null"
],
"maxLength": 128
},
"latest_departure_at": [],
"max_distance_meters": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"max_tasks": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"max_travel_time_seconds": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"return_to_start": {
"type": [
"boolean",
"null"
]
},
"shifts": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"end": {
"type": [
"array",
"null"
]
},
"from": [],
"start": {
"type": [
"array",
"null"
]
},
"to": []
}
},
"maxItems": 6
},
"skills": {
"type": "array",
"items": {
"type": "string",
"maxLength": 64
}
},
"start": {
"type": [
"array",
"null"
]
},
"time_window": {
"type": [
"array",
"null"
]
}
}
}
}
},
"additionalProperties": false,
"required": [
"vehicles"
]
}
Request example
{
"vehicles": [
{
"id": "van-1",
"start": {
"lat": 45.5017,
"lng": -73.5673
},
"return_to_start": true,
"capacity": [
10
],
"time_window": [
"2026-11-02T08:00:00-05:00",
"2026-11-02T17:00:00-05:00"
]
},
{
"id": "van-2",
"start": {
"lat": 45.5017,
"lng": -73.5673
},
"return_to_start": true,
"capacity": [
10
]
}
],
"jobs": [
{
"id": "order-1001",
"location": {
"lat": 45.5088,
"lng": -73.554
},
"service_seconds": 300,
"amount": [
2
]
},
{
"id": "order-1002",
"location": {
"lat": 45.4972,
"lng": -73.5786
},
"service_seconds": 300,
"amount": [
3
],
"time_windows": [
[
"2026-11-02T09:00:00-05:00",
"2026-11-02T12:00:00-05:00"
]
]
},
{
"id": "order-1003",
"location": {
"lat": 45.5231,
"lng": -73.5817
},
"service_seconds": 240,
"amount": [
1
]
}
]
}
Response
What a successful call returns: the schema of `data` and `meta`, with a description of each field, followed by an example response from a real call.
POST /v1/routing/optimize
Response schema
{
"description": "A plan: one route per vehicle that received work, the work no vehicle could take, and totals. `arrival` is written in Eastern time (America/Toronto); use `arrival_epoch` for arithmetic. A problem above the synchronous threshold, or one sent with `\"mode\": \"async\"`, answers 202 with a job instead (see the 202 example); collect the plan from GET /v1/jobs/{jobId}/result.",
"data": {
"type": "object",
"properties": {
"routes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"vehicle_id": {
"type": "string",
"description": "The caller's vehicle id."
},
"stops": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"start",
"job",
"pickup",
"delivery",
"break",
"end"
],
"description": "What happens at this stop. `start` and `end` are the vehicle's own endpoints; `pickup` and `delivery` are the two legs of a shipment."
},
"id": {
"type": "string",
"description": "The caller's id of the job or shipment served here. Absent on start, end and break."
},
"shipment_leg": {
"type": "string",
"enum": [
"pickup",
"delivery"
],
"description": "Which leg of the shipment `id` names. Present on shipment stops only."
},
"arrival": {
"type": [
"string",
"null"
],
"description": "When the vehicle arrives, ISO 8601 with offset. Null when the engine reported no time."
},
"arrival_epoch": {
"type": [
"integer",
"null"
],
"description": "`arrival` as Unix seconds."
},
"arrival_offset_seconds": {
"type": [
"integer",
"null"
],
"description": "Seconds from the route's start to this stop. Present instead of `arrival` and `arrival_epoch` only when nothing in the request carried a time: no vehicle `time_window` and no job time window."
},
"distance_meters": {
"type": [
"integer",
"null"
],
"description": "Distance driven from the route start to this stop, cumulative."
},
"duration_seconds": {
"type": [
"integer",
"null"
],
"description": "Driving time from the route start to this stop, cumulative."
},
"leg_distance_meters": {
"type": "integer",
"description": "Distance of the hop into this stop from the previous one."
},
"leg_duration_seconds": {
"type": "integer",
"description": "Driving time of the hop into this stop from the previous one."
},
"load": {
"type": "array",
"items": {
"type": "number"
},
"description": "What the vehicle carries when it leaves this stop, one figure per capacity dimension, in the order of `meta.capacity_dimensions`."
}
},
"required": [
"type"
]
},
"description": "The stops in driving order, start and end included."
},
"stop_count": {
"type": "integer",
"description": "Number of entries in `stops`."
},
"task_count": {
"type": "integer",
"description": "Jobs and shipment legs served on this route (start, end and breaks not counted)."
},
"distance_meters": {
"type": "integer",
"description": "Total distance driven."
},
"duration_seconds": {
"type": "integer",
"description": "Total driving time. Service and waiting are reported separately."
},
"service_seconds": {
"type": "integer",
"description": "Total service time at the stops."
},
"waiting_seconds": {
"type": "integer",
"description": "Total time spent waiting for time windows to open."
},
"geometry": {
"type": [
"string",
"null"
],
"description": "Encoded polyline (precision 5) of the whole route. Null unless `options.geometry` was true."
}
},
"required": [
"vehicle_id",
"stops"
]
},
"description": "One route per vehicle that received work. A vehicle left idle does not appear."
},
"unassigned": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": [
"string",
"null"
],
"description": "The caller's id of the job or shipment that no vehicle took."
},
"shipment_leg": {
"type": [
"string",
"null"
],
"enum": [
"pickup",
"delivery",
null
],
"description": "For a shipment, which leg was left over."
},
"reason": {
"type": "string",
"description": "Why it was left over, as reported by the engine; `unspecified` when the engine gave no reason."
}
}
},
"description": "Work no vehicle could take. Empty when everything was placed."
},
"summary": {
"type": "object",
"properties": {
"routes": {
"type": "integer",
"description": "Routes in the plan (vehicles with work)."
},
"assigned": {
"type": "integer",
"description": "Tasks placed on a route."
},
"unassigned": {
"type": "integer",
"description": "Tasks left in `unassigned`."
},
"distance_meters": {
"type": "integer",
"description": "Sum of every route's distance."
},
"duration_seconds": {
"type": "integer",
"description": "Sum of every route's driving time."
}
}
}
},
"required": [
"routes",
"unassigned",
"summary"
]
}
}
Response example
{
"request_id": "req_01k8zq3m5n7p9r1t3v5x7z9b2d",
"capability": "routing.optimize",
"billing": {
"unit": "node",
"quantity": 5,
"counted": true,
"breakdown": {
"nodes": 5,
"jobs": 3,
"shipments": 0,
"shipment_nodes": 0,
"vehicles": 2,
"vehicle_nodes": 2
}
},
"data": {
"routes": [
{
"vehicle_id": "van-2",
"stops": [
{
"type": "start",
"arrival": "2026-11-02T08:31:07-05:00",
"arrival_epoch": 1793626267,
"distance_meters": 0,
"duration_seconds": 0,
"load": [
6
]
},
{
"type": "job",
"arrival": "2026-11-02T08:35:11-05:00",
"arrival_epoch": 1793626511,
"distance_meters": 1839,
"duration_seconds": 244,
"leg_distance_meters": 1839,
"leg_duration_seconds": 244,
"id": "order-1001",
"load": [
4
]
},
{
"type": "job",
"arrival": "2026-11-02T08:47:56-05:00",
"arrival_epoch": 1793627276,
"distance_meters": 5338,
"duration_seconds": 709,
"leg_distance_meters": 3499,
"leg_duration_seconds": 465,
"id": "order-1003",
"load": [
3
]
},
{
"type": "job",
"arrival": "2026-11-02T09:00:00-05:00",
"arrival_epoch": 1793628000,
"distance_meters": 9220,
"duration_seconds": 1193,
"leg_distance_meters": 3882,
"leg_duration_seconds": 484,
"id": "order-1002",
"load": [
0
]
},
{
"type": "end",
"arrival": "2026-11-02T09:08:17-05:00",
"arrival_epoch": 1793628497,
"distance_meters": 10687,
"duration_seconds": 1390,
"leg_distance_meters": 1467,
"leg_duration_seconds": 197,
"load": [
0
]
}
],
"stop_count": 5,
"task_count": 3,
"distance_meters": 10687,
"duration_seconds": 1390,
"service_seconds": 840,
"waiting_seconds": 0,
"geometry": null
}
],
"unassigned": [],
"summary": {
"routes": 1,
"assigned": 3,
"unassigned": 0,
"distance_meters": 10687,
"duration_seconds": 1390
}
},
"meta": {
"engine": "sroute2",
"engine_version": "1.6.1",
"solve_id": "fbae5d6c-71ab-43c5-b619-6ef6fe3eaee7",
"capacity_dimensions": [
"0"
]
}
}
Example response when queued (202)
{
"request_id": "req_01k8zq3m5n7p9r1t3v5x7z9b2d",
"capability": "routing.optimize",
"billing": {
"unit": "node",
"quantity": 5,
"counted": true,
"breakdown": {
"nodes": 5,
"jobs": 3,
"shipments": 0,
"shipment_nodes": 0,
"vehicles": 2,
"vehicle_nodes": 2
}
},
"data": {
"job": {
"id": "job_01k8zq4a6c8e0g2j4l6n8q0s2u",
"status": "queued",
"capability": "routing.optimize",
"poll_after_ms": 1000,
"result_url": "/v1/jobs/job_01k8zq4a6c8e0g2j4l6n8q0s2u/result",
"cancel_url": "/v1/jobs/job_01k8zq4a6c8e0g2j4l6n8q0s2u/cancel"
}
},
"meta": {
"mode": "async"
}
}
Routing rules by engine
Which engine plans each optional routing rule, and from which engine version. A rule the chosen engine cannot plan is refused with VALIDATION_FAILED before anything is solved or billed; an approximated rule is accepted with a RULE_APPROXIMATED warning.
| Rule | Fields | sroute | sroute2 | sroute2t | sroute3 |
|---|---|---|---|---|---|
max_ride |
shipments.*.max_ride_seconds
constraints.max_ride_seconds
|
Not supported | Not supported | Native 1.0.19 or later | Native 1.0.0 or later |
latest_departure |
vehicles.*.latest_departure_at
|
Not supported | Not supported | Native 1.0.19 or later | Native 1.0.0 or later |
groups |
jobs.*.group
shipments.*.group
|
Approximated | Approximated | Native 1.0.19 or later | Native 1.0.0 or later |
goods_classes |
constraints.incompatible_goods_classes
jobs.*.goods_class
shipments.*.goods_class
|
Approximated | Approximated | Native 1.0.19 or later | Native 1.0.0 or later |
deliveries_before_pickups |
constraints.deliveries_before_pickups
|
Not supported | Not supported | Native 1.0.19 or later | Native 1.0.0 or later |
waiting_cost |
constraints.waiting_cost
|
Not supported | Not supported | Native 1.0.19 or later | Native 1.0.0 or later |
use_all_vehicles |
constraints.use_all_vehicles
|
Approximated | Native | Native 1.0.19 or later | Native 1.0.0 or later |
merge_stops |
constraints.merge_stops
|
Native | Native | Native | Native |
reload |
constraints.reload
|
Approximated | Approximated | Native 1.0.20 or later | Native 1.0.0 or later |
loading_bays |
constraints.reload.depots.*.loading_bays
constraints.reload.slot_seconds
|
Not supported | Not supported | Native 1.0.20 or later | Native 1.0.0 or later |
ev_range |
vehicles.*.ev_range_meters
vehicles.*.ev_reserve_meters
|
Native | Native | Native 1.0.19 or later | Native 1.0.0 or later |
release |
jobs.*.release_at
|
Approximated | Approximated | Native 1.0.20 or later | Native 1.0.0 or later |
alternatives |
jobs.*.alternatives
|
Approximated | Approximated | Native 1.0.21 or later | Native 1.0.0 or later |
break_skip_policy |
vehicles.*.breaks.*.skip_policy
|
Approximated | Approximated | Native 1.0.21 or later | Native 1.0.0 or later |
break_places |
vehicles.*.breaks.*.places
|
Not supported | Not supported | Native 1.0.21 or later | Native 1.0.0 or later |
shifts |
vehicles.*.shifts
|
Native | Native | Native | Native |
What it costs
Every point the solver must visit or depart from, including each vehicle's start and end.
Every billable response carries a billing block with the unit, the quantity and how the quantity was derived.
Errors it can return
| Status | Code | Description |
|---|---|---|
| 401 | TOKEN_INVALID |
Unknown or revoked API token. |
| 401 | TOKEN_MISSING |
No API token. Send Authorization: Bearer <your token>. |
| 402 | QUOTA_EXHAUSTED |
The included allowance for this capability is used up for the current period. |
| 403 | CAPABILITY_NOT_ENABLED |
This capability is not enabled for your account. See :doc to enable it. |
| 403 | CAPABILITY_NOT_GRANTED_TO_KEY |
Your account has this capability, but this API key was not granted it. |
| 403 | ENGINE_NOT_ENABLED |
This routing engine is not enabled for your account. Omit `engine` or choose an engine that is. |
| 403 | OPEN_API_DISABLED |
Open Platform access is not enabled for this account. |
| 422 | CALLBACK_NOT_VERIFIED |
Verify the callback endpoint first: POST /v1/webhooks/verify and echo the challenge back. |
| 422 | CALLBACK_URL_UNSAFE |
The callback URL must be a public HTTPS endpoint. |
| 422 | SOLVE_INFEASIBLE |
No plan satisfies every constraint. Check time windows, capacities and skills. |
| 422 | UNSUPPORTED_REGION |
That routing region is not available. Use NA, EU, AU or ASIA. |
| 422 | VALIDATION_FAILED |
The request failed validation. See details. |
| 424 | ENGINE_REJECTED |
The routing engine refused this problem. Its reason is included where available. |
| 429 | CONCURRENCY_LIMIT |
Too many solves are already running for this account. Retry when one finishes. |
| 429 | RATE_LIMITED |
Too many requests. Please retry after the indicated delay. |
| 500 | INTERNAL_ERROR |
An unexpected error occurred. Quote the request_id when contacting support. |
| 502 | ENGINE_UNAVAILABLE |
The routing engine is temporarily unreachable. Please retry. |
| 504 | SOLVE_TIMEOUT |
The solve exceeded the synchronous budget. Send the same request with "mode":"async" and collect the result from the job. |