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.statusispartialorinfeasible, 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
string
The HTTP status as text:
BAD_REQUEST, FORBIDDEN, PAYLOAD_TOO_LARGE, UNSUPPORTED_MEDIA_TYPE, INTERNAL_SERVER_ERROR, BAD_GATEWAY or SERVICE_UNAVAILABLE.string
What kind of failure this is. One value per HTTP status; see the table below. Branch on this.
string
A summary for people. With one item in
errors[] it is that item’s message; with several it is the count, as above.string
Identifies this request. The same value is returned in the
x-request-id response header. Quote it when you contact support.array
One entry per problem found in the request. Present on a
400; absent on other statuses.string
What is wrong with this field. See Item codes.
string
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. See Pointers.
string
A description for people. Do not parse it; branch on
code and pointer.integer
On
limit_exceeded only: the cap.integer
On
limit_exceeded only: the count your request has.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 or a break 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.
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. Only the first is a JSON Pointer.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 every problem it finds in one response, up to 100 items, so you can fix them in one pass.
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
?validate_only=true. You get 200 {"valid": true, "warnings": []} or the same 400 the real call would return, with no travel-time fetch and no search. It validates problem and options; a plan is checked against the problem only by the real call.
The parameter takes the literal true or false. Any other value, such as validate_only=1, is a 400 with a plain-text body, not the error body above.
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.
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.
503 Service Unavailable
The service is at capacity, or travel-time lookups are temporarily suspended after repeated failures. Every503 carries a Retry-After header in seconds. Wait that long and retry; 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.status:
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:
string
The job id, or the shipment id for a shipment. A shipment is listed once, not once per stop.
string
What kind of fix can work. One of the four values in Categories. Always present.
array
The rule that kept the task out, as a
code from Reason codes and a message for people.array
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 suggestion sentence and a field:
field names the task by id, as in /jobs/delivery-7/time_windows, not by its index in the request. It uses /jobs/ for a shipment as well. 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_resource 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.
Reason codes
reasons[].code is one of eleven values. Treat a value you do not recognise as ALL_ROUTES_BLOCKED.
SOLVER_LIMIT is the one code that covers two categories, so branch on category and not on the code. The Constraints page lists which rule produces which code.
On evaluate
/v3/routing/evaluate does not search, so unassigned[] and summary.status mean something different there:
summary.statusisfeasibleorinfeasibleand describes the rules your plan breaks. 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 code
SOLVER_LIMIT and a message that mentions a time budget. 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[].code on a few of them. TIME_WINDOW_VIOLATED with category impossible on all of them usually means the job windows and the shifts are on different dates or in different time zones. A timestamp without a UTC offset is accepted and read as UTC, not as local time, so send an offset or Z on every timestamp. UNREACHABLE_LOCATION on all of them usually means coordinates are in [latitude, longitude] order.A job is unassigned with search_limit
A job is unassigned with search_limit
On solve, the default
options.time_limit_s is 1 second. Larger problems need more: raise it, up to 300. On evaluate, search_limit means the task fits but is not in your plan: see On evaluate.Suggest returns no options for a task
Suggest returns no options for a task
The entry carries a
reason, and the top-level truncated flag says whether the answer is final. See When a task gets no options.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 moved or dropped, so if it breaks a hard rule the request fails. 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
Add
?validate_only=true. 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.