V3 is in beta. General availability (GA) is planned for January 2027.
Overview
There are two kinds of “it didn’t work” on the V3 Routing API, and they look different:- The request failed. You get a
4xxor5xxstatus and an error body. Nothing was planned. - The request succeeded but not every job was planned. You get a
200.summary.tasks.unassignedis above0, andunassigned[]says which jobs were left out and why. See Unassigned jobs.
401 or a 429? Those come from the API gateway, before the request reaches the routing service, and do not use the error body below. See Authentication errors and 429 Too Many Requests.
The error body
Every error the routing service returns from/v3/routing/solve, /v3/routing/evaluate and /v3/routing/suggest has this JSON body. That covers the statuses 400, 403, 413, 415, 500, 502 and 503:
400 Bad Request
The HTTP status as text:
BAD_REQUEST, FORBIDDEN, PAYLOAD_TOO_LARGE, UNSUPPORTED_MEDIA_TYPE, INTERNAL_SERVER_ERROR, BAD_GATEWAY or SERVICE_UNAVAILABLE.What kind of failure this is. One value per HTTP status; see the table below. Branch on this.
A summary for people. With one item in
errors[] it is that item’s message; with several it is the count, as above.Identifies this request. The same value is returned in the
x-request-id response header. Quote it when you contact support.What is wrong with this field. See Item codes.
A JSON Pointer (RFC 6901) to the offending place in the request body. Absent when the problem has no location, such as a body that is not JSON or a query parameter, which the message names. See Pointers.
On
limit_exceeded only: the count your request has. For options.time_limit_s it is the value you sent, rounded up to whole seconds.Pointers
A pointer is a path from the root of the body you sent. Array elements are addressed by index.
For an unknown field the pointer usually ends at the unknown key. Inside a job, a shipment, a break or a relation it can stop at the enclosing object, such as
/problem/jobs/0; the message always names the field. For a missing field the pointer names the object that lacks the field, and the message names the field. An unknown query parameter has no pointer; the message names it.
A problem that is only found after travel times are fetched can carry a less precise pointer: it can name a whole collection, or be absent. One case points at a key that does not exist in the request: when the coordinates span more than one region, the pointer is /problem/locations. The coordinates to check are the ones on your jobs, shipments and depots, and on the start and end of each shift.
Pointers, fields and reasons
Three response fields tell you where to look. The first two are JSON Pointers.How many problems are reported
- A body that cannot be read at all (bad JSON, unknown field, missing field, wrong type) reports the first problem only. Fix it and resend.
- Once the body reads cleanly, validation reports the problems it finds together, up to 100 items. It may list only the first problem of each job, shipment, vehicle or depot, and it checks what ties entries together (vehicle references, relations, quotas, the objective) only once every entry is valid on its own. A corrected request can therefore show new problems: fix and resend until it passes.
- A problem with the
plan, which is matched to the problem only after travel times are fetched, is reported on its own, one at a time.
Status codes
Branch on the HTTP status first, then on
code. Read code only on a status that has one in this table.400 Bad Request
Unknown fields are a400, never ignored. So are a misspelled field, a wrong type and a field the API does not support yet. A request that returns 200 was read exactly as you sent it.
400: unknown field
POST /v3/routing/validate/solve, /v3/routing/validate/evaluate or /v3/routing/validate/suggest, the one that matches the endpoint. You get 200 {"valid": true, "warnings": []} or the same 400 the real call would return (or its 403, for a feature your account does not have), with no travel-time fetch and no search. It validates problem and options. Coordinates that span more than one region and the plan (its references and whether it imports, warm-start plan on solve included) are checked only by the real call, after the travel-time fetch; suggest’s own check that plan leaves a task to place does run here.
The body is checked as the chosen endpoint checks its own, so a field that endpoint does not take, such as options on evaluate, is the same unknown_field 400.
The validate endpoints take no query parameter, and neither do solve, evaluate and suggest. Any query parameter, including a misspelt name or the removed validate_only, is a 400 with the error body above and an item of code unknown_field; query items carry no pointer, the parameter is named in message.
413 and 415
The 10 MiB limit applies to the body after gzip decompression. To send a compressed body, setContent-Encoding: gzip and keep Content-Type: application/json. See Limits.
502 Bad Gateway
The solver could not get travel times for your locations from the routing data provider. The request itself may be fine. Retry with backoff; if it keeps failing for the same request, contact support with therequest_id.
Only a solve or a suggest returns this error, and only with options.travel_fallback: "fail". By default, travel times that cannot be fetched, for any reason, are estimated: /v3/routing/solve plans on the estimate and summary.travel_estimated says so; /v3/routing/suggest ranks on it and sets travel_estimated. options.travel_fallback takes "estimate" (the default) or "fail", which returns the 502 or 503 instead. /v3/routing/evaluate has no such option and always estimates, setting summary.travel_estimated. See Distance matrices.
Coordinates that span more than one region or continent are a different case: that is a 400 with item code invalid_value, and usually means a coordinate is in [latitude, longitude] order. V3 coordinates are [longitude, latitude]. See Distance matrices for how travel data is fetched.
429 Too Many Requests
Requests are rate limited by the API gateway. A request over the limit is rejected with429 before it reaches the routing service, so its body is not the error body on this page: detect it from the HTTP status and do not read code or request_id from it. Back off and retry. See Authentication. POST /v3/routing/runs also answers 429, with Retry-After: 30, while 10 runs of your account are queued or running: see Async runs.
503 Service Unavailable
The service is at capacity, or, on a solve or suggest withtravel_fallback: "fail", travel-time lookups are temporarily suspended after repeated failures. Every 503 carries a Retry-After header, always 1 second. It is a floor, not a forecast: wait that long and retry, and add backoff if it repeats.
503 Service Unavailable
Item codes
errors[].code on a 400. New values can be added, so handle a value you do not recognise as a generic invalid request.
Authentication errors
A request with a missing or unrecognised key is rejected by the API gateway with401 before it reaches the solver. The body is not the error body above, and comes in two shapes:
Authorization: YOUR_API_KEY, with no Bearer prefix. See Authentication.
Unassigned jobs
A200 from /v3/routing/solve does not mean every job was planned. Check summary.tasks:
A job is one task and a shipment is one task. Every task is on a route when
unassigned is 0; none is when served is 0.
This section is the reference for each category and reason code. For the order to read them in, how to confirm a diagnosis with /v3/routing/evaluate and /v3/routing/suggest, and the ranked fixes, see Handling infeasibility.
The plan the solver returns never breaks a hard constraint. A job that cannot be served within the rules is left out and reported, with a reason:
What kind of fix can work. One of the four values in Categories. Always present.
Never empty. Each reason names the
constraint that kept the task out on at least one vehicle and carries a message for people. A task lists each constraint once. For priced_out and time_limit no rule did, so there is one reason without a constraint. See Reasons.A best-effort hint at the smallest change that would let the task fit. Empty when no simple change exists, such as for a skills mismatch.
op, an amount in unit, a message sentence and a pointer:
pointer is a JSON Pointer into the request body, as in /problem/jobs/6/time_windows for the seventh job. A shipment is under /problem/shipments/, as in /problem/shipments/0/load. See Pointers, fields and reasons.
Categories
Start withcategory. It tells you what kind of fix can work.
One case of
impossible depends on the plan. A task in a same_vehicle or same_day relation whose partner is already planned can only go on that vehicle or that day. When no shift there can serve it, the task is impossible, although another vehicle or day could take it if the partner moved.
Reasons
Each reason names oneconstraint that refused the task, in the same vocabulary as evaluate’s violation codes, and carries a message for people. There is no separate outcome code: category says what kind of fix can work, and constraint says which rule refused the task. A priced_out or time_limit task has one reason with no constraint.
A task lists each constraint once, unreachable first, so a task refused for capacity on one vehicle and for its time window on another has two reasons, capacity and time_window. Treat a constraint you do not recognise as unknown.
The Constraints page lists which rule produces which constraint.
On evaluate
/v3/routing/evaluate does not search, so unassigned[] and the score mean something different there:
summary.score.violation_countcounts the rules your plan breaks;0means none. It says nothing about tasks the plan leaves out. Broken rules are reported as violations.unassigned[]lists every task that is in no route of your plan, in the same shape as above. The category then describes whether the task could be added to the plan as it stands:
A task that fits comes back with one reason and no
constraint, unless its all_or_none group holds it back (all_or_none) or its all-or-none crew cannot be placed together (synchronized, time_lag). Evaluate has no time budget and takes no options, so there is nothing to raise: add the task to plan, or ask /v3/routing/suggest where it fits best.
Troubleshooting
I get a 400 for a field that used to work in V2
I get a 400 for a field that used to work in V2
V3 uses snake_case names and a different structure, and rejects every field it does not define. The
pointer shows which field. See Migration from V2 for the mapping.Every job comes back unassigned
Every job comes back unassigned
Look at
category and reasons[].constraint on a few of them. time_window with category impossible on all of them usually means the job windows and the shifts are on different dates or in different time zones. unreachable on all of them usually means coordinates are in [latitude, longitude] order.A job is unassigned with time_limit
A job is unassigned with time_limit
On solve, the default
options.time_limit_s is 150 ms plus 4 ms per task. Larger problems may need more: raise it, up to 60. On evaluate, time_limit means the task fits but is not in your plan: see On evaluate.Suggest returns no insertions for a task
Suggest returns no insertions for a task
The entry carries a
reason whose code says why: only infeasible is final, while time_limit and candidates_exhausted can change with a larger options.time_limit_s or options.max_insertions. For a crew, candidates_exhausted does not change with either; /v3/routing/solve may still place it. See When a task gets no insertions.Solve rejects my warm-start plan with a 400
Solve rejects my warm-start plan with a 400
A stop inside a route’s
locked_count cannot be dropped, so if it breaks a hard rule the request fails. errors[] lists each such stop (up to 100), each with the pointer of its stop (/plan/routes/{i}/stops/{k}) and the rule in the request’s terms, such as time window or shift end, capacity or eligible_vehicles; /v3/routing/validate/solve returns 200 for those cases. On /v3/routing/runs the run ends failed instead. A stop marked pinned: true outside a locked_count prefix (a shipment leg whose other leg is inside counts as inside) never fails the request: one that cannot be served on its vehicle comes back in unassigned[] with the rule that refuses it. Send the same problem and plan to /v3/routing/evaluate: it scores the plan as it is and lists each rule it breaks, stop by stop.I get a 415 although I send JSON
I get a 415 although I send JSON
The
Content-Type header must be application/json. Some HTTP clients send text/plain or a form type unless told otherwise. If you compress the body, the only accepted Content-Encoding is gzip.I want to check requests in CI without being billed
I want to check requests in CI without being billed
Send it to
POST /v3/routing/validate/solve, /v3/routing/validate/evaluate or /v3/routing/validate/suggest. The request goes through the same validation as a real call and returns before any travel-time fetch or search.Limits
Request limits and what you get when you exceed one.
Authentication
API keys, the
401 response and rate limiting.Evaluate
See exactly which rules a plan breaks.
API reference
Request and response schema for every endpoint.