Editing plans
Editing plans
The plan calls price and judge a plan that you hold. The plan is never stored: each call carries the whole plan and answers the plan it produced. This page describes the edit calls and the distance snapshot that makes repeated edits of one plan fast.
The plan
Every plan call takes the same plan as plan re-costing (POST /v1/plan/recost):
vehicles, each with anidand astart, and optionally anend,return_to_start, a capacity, skills and a working window;jobs, each with anidand alocation, and optionally a service time, an amount, skills and time windows;routes, each avehicle_idand itsstopsas ordered job ids. A job on no route is unassigned.
The edit calls require an id on every vehicle and job, because an edit names them.
The edit calls
| Call | Capability | What it does |
|---|---|---|
POST /v1/plan/recost |
plan.recost |
Prices the plan exactly as given. |
POST /v1/plan/validate |
plan.validate |
Judges the plan exactly as given, without the map payload. |
POST /v1/plan/resequence |
plan.resequence |
Gives the route of vehicle_id the stop order in stops. |
POST /v1/plan/move |
plan.move |
Moves the jobs in stops to the route of vehicle_id at position. |
POST /v1/plan/remove |
plan.remove |
Takes the jobs in stops off their routes. |
An edit call applies its edit and prices the edited plan exactly as plan re-costing prices it: the
routes and summary of the answer are those a re-costing of the edited plan returns. The answer
adds plan, the edited plan in the shape it is sent (routes with ordered stop ids, and
unassigned: every job on no route), so it can replace the plan you hold.
- Positions count from 1, in driving order. A route sent with
reverseis read reversed, and comes back inplanalready reversed. plan.movetakes the stops out first:positioncounts the stops of the target route without them, and an omittedpositionplaces them after the last stop. A stop may come from any route or be a job on no route.move.feasibleis true when the routes the move touched keep every stated rule.plan.removerefuses a job that is on no route. A route left without stops is priced from its start to its end.
Violations are reported on the stops and routes, never refused. Every call is billed per node: the stops of the edited plan that were priced.
The search calls
| Call | Capability | What it does |
|---|---|---|
POST /v1/plan/insert |
plan.insert |
Places the new jobs in insert at their cheapest positions. |
POST /v1/plan/refine |
plan.refine |
Re-orders the stops of chosen routes around frozen stops. |
POST /v1/plan/what-if |
plan.what_if |
Prices and ranks alternative plans. |
Every distance and time in their answers is measured on the road, as plan re-costing measures it. With a distance snapshot, the candidates are ranked on the snapshot's road matrix, which is faster. The positions and orders found can then differ from those found without it, but every figure answered is measured the same way.
Inserting jobs
- The jobs in
insert(up to 20) are placed one at a time, in the order given. Each goes to the position that keeps the plan within its rules and adds the least driver time (objectiveduration, driving plus waiting) or distance (distance). Nothing else in the plan moves. insertin the answer lists, per job, whether it wasplacedand up tolimitchoices (default 3), best first, each with its route, position, added distance and time, and arrival. A job that no position can take stays inunassigned.- Without a snapshot, the positions beside the
neareststops (default 25) and every empty route are measured. - Billed per node: the stops of the plan plus the new jobs.
Refining routes
- The stops of the routes of
vehicle_ids(default: every route) are re-ordered within and between those routes. Stops infreezestay on their route in their order. Unassigned jobs and other routes do not take part. - The new order is kept only when it is better than the plan as sent: fewer unreachable legs, then fewer violations, then less driver time, then less distance.
refine.changedsays whether it was kept, andrefine.beforegives the totals of the plan as sent. - With a snapshot, up to 600 stops are refined. Without a usable snapshot, up to 60 stops on 5 routes are refined by the routing engine. A larger refine answers
PLAN_REFINE_REFUSED(422) with the reason inreason:no_snapshot,too_large,stops_left_outorfailed. Refine such a plan withPOST /v1/routing/refine, which runs as a job. - Single jobs only: a request with pickup and delivery pairs is refused.
- Billed per node: the stops priced.
Comparing plans
plansholds 2 to 10 alternatives over thevehiclesandjobsof the request, each with itsroutesand an optionalid. Each plan is priced and judged as plan re-costing does.- The plans are ranked by fewer unreachable legs, then fewer violations, then the
objective. The answer givesranking(plan indices, best first),best, and each plan with its rank, summary and routes. Leg geometry is included only withoptions.geometry. - With
rank_onlyand a snapshot, the plans are ranked by their travel on the snapshot's road matrix and are not priced (scoredis false). Time windows, capacity and the other rules are not part of that ranking. - Billed per node: the stops of every plan.
Distance snapshots
Each call measures every leg of the plan on the road network. While one plan is edited, most legs stay the same from one call to the next. A distance snapshot keeps the legs that were measured, so a later call measures only the legs it has not seen.
-
Open a snapshot for the plan's region and vehicle profile:
POST /v1/plan/matrix {"region": "NA", "profile": "car"}The answer holds the snapshot id in
matrix, itsexpires_atand thenodethat holds it. -
Send the id as
matrixwith every plan call of the same region and profile:{"vehicles": [...], "jobs": [...], "routes": [...], "matrix": "tile2.3f0c9a5e7b1d4c2a9e8f6d5c4b3a2910"} -
Close it when the plan is no longer edited:
DELETE /v1/plan/matrix/{matrix}.
The answer of a call with matrix is the same as the answer without it, byte for byte, and is billed
the same. Only the time changes: legs already measured are read from the snapshot.
The response header X-Plan-Matrix tells what happened to the snapshot:
| Value | Meaning |
|---|---|
used |
Legs were read from the snapshot and the new ones were added to it. |
expired |
The snapshot no longer exists. The call was measured without it. Open a new one. |
unavailable |
The snapshot could not be reached. The call was measured without it. |
mismatch |
The snapshot belongs to another region, profile or account, or the id is not a snapshot id. The call was measured without it. |
A snapshot never makes a call fail: whatever happens to it, the call is answered as without
matrix.
Limits
- A snapshot is removed one hour after its last use, and eight hours after it was opened at the latest.
- An account holds a limited number of snapshots at a time, and a snapshot holds at most 2,000
points. A refused opening answers
SNAPSHOT_LIMIT_REACHED(429) with the limit reached inlimit:snapshots_per_business,points_per_snapshotornode_memory.
Opening and closing a snapshot are free. They are available to an account with any plan
capability, and to any API key granted one.
What a snapshot holds
The points of the plan and the road legs measured between them: distance, duration and the line of each leg. No order, address, vehicle or other data is kept. The snapshot is held in memory only, and everything in it is removed when it is closed or expires.
Editing in the browser or app
A plan editor in a web page or a mobile app calls the plan endpoints directly. The map SDKs include one, set up step by step in The plan editor. It must not hold your API token, so it uses one of two credentials:
| Credential | How it is obtained | Where it works |
|---|---|---|
Plan editor token (sr_pet_…) |
Your server calls POST /v1/plan/token with the API token and passes the answer to the editor. |
From the origins given when it was issued; from anywhere when none were given. |
Map key (smk_…) |
Switch on Allow plan editing for the key on the Map keys page of the portal. | From the web origins and apps on the key's allowlist. |
Both are sent as Authorization: Bearer <credential>. A native app also sends X-SMap-App (and
X-SMap-App-Cert on Android), as the map SDKs do.
POST /v1/plan/token
Authorization: Bearer <API token>
{"ttl_seconds": 900, "origins": ["dispatch.example.com"]}
The answer holds access_token, expires_in, expires_at, scopes and origins. A token is
valid for 15 minutes by default and for one hour at most. Request a new one before it expires; a
token cannot issue another token.
- Both credentials call the plan endpoints only,
POST /v1/plan/tokenexcepted: re-costing, validation, the edit calls, the search calls and distance snapshots. Every other endpoint refuses them withNOT_AUTHORIZED, reasonplan_routes_only. - A token carries the plan capabilities of its API key, and every call checks the key again.
Revoking the key ends the token (
SESSION_REVOKED, reasonkey_revoked), and so does a change of the account's capabilities (reasonentitlements_changed). An expired token answersSESSION_EXPIRED. - A map key works only while the account holds the plan capabilities. Plan editing is off for every key until it is switched on.
- A call from another origin or app answers
NOT_AUTHORIZED, reasonorigin_not_allowed. - Calls are billed, limited and checked exactly as calls made with the API token.
- The plan endpoints answer the browser's preflight. An answer to a call made with either credential
carries
Access-Control-Allow-Originfor the calling page and exposesX-Plan-Matrix. An answer to a call made with the API token never does: keep the API token on your server.