V3 is in beta. General availability (GA) is planned for January 2027.
https://api.solvice.io, the key goes in Authorization with no Bearer prefix, and a field the schema does not define is 400 with item code unknown_field.
Add V3 to an existing application
Use this path when the application exists and V3 should plan its routes: a synchronousPOST /v3/routing/solve first, then evaluate and suggest.
- Read Authentication and Requests. Write coordinates as
[longitude, latitude]. Pitfall: a401comes from the gateway with an empty body or one that is not the error body; branch on the HTTP status before you parse JSON. - Read Your first solve. Send a minimal
problemwithjobs[]andvehicles[]toPOST /v3/routing/solvewithoptions.time_limit_sof a few seconds. Pitfall: a timestamp without an offset returns400with apointerto the field; write+02:00orZon everyfrom,toand window. - Read A request at a glance, then Jobs and Vehicles and shifts. One
jobs[]entry per visit, oneshipments[]entry per pickup-then-delivery pair, oneshifts[]entry per vehicle per working day, each with anid. Pitfall: a field name carried over from another routing API returns400with item codeunknown_field; fix the field at thepointerusing the reference’s name. - Read Capacity, Time windows and Skills. Turn on only the rules your data asserts. Pitfall: a misspelt capacity dimension silently gives the vehicle capacity
0in it, and the job comes back inunassigned[]withcapacity; match names exactly, and omitcapacityrather than send{}to mean “no limit”. - Read Objective and the V3 defaults. Pitfall: an omitted
objectivesilently prices every route at 35 per route hour and 0.20 per kilometre; setobjective.coststo your rates before comparing plans. - In CI, send every request to
POST /v3/routing/validate/solve(Dry run):200 {"valid": true, "warnings": []}or the real call’s400, not billed. Pitfall: validate checksproblemandoptionsonly; a bad warm-startplanand coordinates spanning more than one region are reported by the real call alone. - Read Response and Unassigned tasks. Pitfall: a plan that leaves tasks out is still HTTP
200, so a client that checks the status alone silently ships an incomplete plan; checksummary.tasks.unassigned_required, thenunassigned[].categoryandreasons[].constraint. - Read Solve time and Client timeout. Pitfall:
time_limit_sbounds the search only, so an HTTP timeout equal to it silently aborts requests about to succeed; set it to the budget plus the measured overhead. A budget above60returns400with item codelimit_exceededat/options/time_limit_s; use an async run instead. - Read Evaluate and Violations. Send
problemplus a returnedplantoPOST /v3/routing/evaluateto score a hand-edited plan. Pitfall: evaluate takes nooptions; sending one returns400with item codeunknown_field. A plan with violations is200: readsummary.score.violation_count. - Read The request and Applying a suggestion. Add the new task to
problem, leave it out ofplan, and callPOST /v3/routing/suggest. Pitfall: an emptyinsertionslist is200with areason.code, and onlyinfeasibleis final; raiseoptions.time_limit_sfortime_limitandoptions.max_insertionsforcandidates_exhausted. - Read What your client must do. Pitfall: a generated client with closed enums fails on a new
category,constraintor errorcode; accept unknown values and never parsemessage.
Migrate from V2
Use this path when you call/v2/vrp/* today. How to port a request is the procedure; this path adds the pitfalls.
- Read Overview and Endpoints and lifecycle.
POST /v2/vrp/sync/solvebecomesPOST /v3/routing/solve; the job lifecycle (POST /v2/vrp/solve,GET /v2/vrp/jobs/{id}) becomesPOST /v3/routing/runsandGET /v3/routing/runs/{id}. Pitfall: the V2hookhas no counterpart; a body withcallbackreturns400with item codenot_supportedat/callback; poll the run. - Read Conventions. Pitfall: V2 read a timestamp without an offset as UTC and accepted a zone id such as
[Europe/Brussels]; V3 returns400for both; send the part before[, with its offset. Coordinates become[longitude, latitude]; a swapped pair is not an error, so every job silently comes backunassignedwithunreachable. - Read V2 fields with no direct V3 equivalent and Feature parity. Decide each untranslated field’s replacement before you rewrite anything. Pitfall: a leftover V2 key returns
400with item codeunknown_field; thepointernames it. - Read Vehicles, Shifts and Jobs. Wrap the problem in
problem, rename to snake_case with unit suffixes, give every shift anid. Pitfall: capacity moves from a positional array of up to 5 to a named map of up to 4 dimensions; a fifth name returns400with item codelimit_exceededat/problem/vehicles. - Read Relations and Pickup and delivery pairs. Each
PICKUP_AND_DELIVERYpair becomes ashipments[]entry. Pitfall: a relation that names the shipment’s own id returns400with item codenot_supported; name its legs{shipment}:pickupand{shipment}:delivery. - Read Default objective and From weights to prices. Pitfall: a native V3 request without
objectiveis silently optimised for the V3 defaults, not for what your V2 weights expressed; set each price inobjective.costsand on the fields (drop_cost,per_late_hour). - Read Options.
?millis=5000becomesoptions.time_limit_s: 5. Pitfall: V2 priced TomTom traffic at request time, while a V3 request withouttravel.departure_timesilently defaults to free-flowosm; settravel.engine: "tomtom"to keep the V2 behaviour. A budget above 60 seconds, which V2 lowered, returns400with item codelimit_exceeded. - Read Existing plans. Replace
initialResourceandinitialArrivalwith a request-levelplanwhose routes namevehicleandshift. Pitfall: aplanfreezes nothing, so dispatched stops silently move on re-solve; setplan.routes[].locked_count. - Send the ported body to
POST /v3/routing/validate/solveand fix eacherrors[]item at itspointer, then solve the same instance on both APIs and read it with Response mapping. Pitfall: V2 response times are UTC, V3 writes them in the offset of the shift’sfrom; a parser that assumesZsilently shifts every arrival. Compareunassigned[], route totals and stop order before switching traffic.
Build a dispatch UI with warm starts and re-planning
Use this path when dispatchers edit routes during the day. Theplan object is the state your UI holds: the API keeps none.
- Read Plan and Warm-starting with
plan. Storeplanfrom every response. Pitfall: onsolve,planis a hint; an unlocked stop that breaks a hard rule is silently moved or dropped tounassigned[]; setlocked_counton the dispatched prefix, or"pinned": trueon a stop that must keep its vehicle. - Re-plan by sending
problemplus the editedplantoPOST /v3/routing/solve. Pitfall: a locked or pinned stop that breaks a hard rule returns400with item codeinvalid_valueat/plan; send the same body toPOST /v3/routing/evaluateto see which rule. - Read The plan shape and Violations. After each drag-and-drop, call
POST /v3/routing/evaluateand renderroutes[].stops[].violations,routes[].violationsandsummary.score.violation_count. Pitfall: a window priced withper_late_houris not a violation when missed, so a UI that rendersviolations[]alone silently hides late stops; renderstops[].lateness_stoo. - Read What evaluate checks, and when. Pitfall: an unknown stop id returns
400with item codeunknown_reference, a stop listed twiceduplicate_id, a shipment split across routesinvalid_value, all at/plan/routes/{i}/stops/{k}, and the real call checks these when it imports the plan; check the plan in your UI first. - Read Reading an insertion and Applying a suggestion. For one new order, add it to
problem, leave it out ofplan, callPOST /v3/routing/suggest, and showplacements[].service_startanddelta.costper insertion. Pitfall: each task is ranked against the plan you sent, so applying one insertion silently invalidates the others; call suggest again with the updated plan. - Read When a task gets no insertions. Branch on
reason.code, never onreason.message. Pitfall:options.max_insertionsabove10returns400with item codelimit_exceeded, and0returnsout_of_range; neither is clamped. - Read Shipments and related tasks if orders are shipments or crews. Pitfall: a shipment with one leg in
planreturns400; leave both legs out. - Read Submit a run, Poll a run and Fetch the plan. For the overnight plan, or more than 400 tasks,
POST /v3/routing/runswith the solve body, store theid, pollGET /v3/routing/runs/{id}untilstatusis final, thenGET /v3/routing/runs/{id}/result. Pitfall: 401 or more tasks onPOST /v3/routing/solvereturns400with item codelimit_exceededat/problem/jobs;/resultbeforesucceededreturns409with amessagestartingrun_not_finished. - Read Cancel a run and Limits. Pitfall: a cancelled run counts toward the 10 queued-or-running runs until its worker stops; the eleventh submission returns
429withRetry-After: 30. - Read Re-optimizing from an existing plan and See how fast the search converges. Size
options.time_limit_sfor re-plans fromsearch_progress. Pitfall: an omittedseedis42, so a “try again” button silently returns the same plan; send a differentseed. Call the API from your server, never from the browser (Server-side proxy pattern).
Model a customer’s operation as an AI agent
Use this path when an agent turns a description of an operation into requests.- Read AI agents: what to ask before you model, and what never to default.
- Read the Constraints overview table and map each business rule to one field, hard or soft. Pitfall: a rule the schema has no field for cannot be expressed by inventing one; that returns
400with item codeunknown_field. Tell the user what cannot be modelled instead of approximating it silently. - Send every request to
POST /v3/routing/validate/solvebeforePOST /v3/routing/solve. It is not billed. Pitfall:validatedoes not readplan; the real call checks a warm start. - Read Unassigned jobs and Unassigned tasks. Pitfall: a
relaxations[]entry widens a window or reduces a load; applying it silently changes the customer’s rule. Surface it and let the user decide. - Read Problem size. Pitfall: more than 400 tasks on
POST /v3/routing/solvereturns400with item codelimit_exceeded; submit a run instead.
/v2/vrp/* today, the V2 tab, and the full OpenAPI document: these pages name every field you need.