Migration guide
Moving from Google Distance Matrix
This one is not a base URL change. The request shape, the response shape and the auth header all differ, so here is every field, and the two behaviours that will bite you if nobody points at them first.
Most teams arriving here are not leaving Google because it is bad. They are leaving because the matrix call is the line on the bill that keeps growing, and because almost nothing they compute with it needs a live traffic estimate.
The work below is real — this is a different API, not a different hostname — but it is mostly deletion. Three of the seven items on the checklist are code you get to remove.
The change
migrate.diff
Removed: - POST https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix
Added: + POST https://api.waymatrix.io/v2/matrix/driving-car # and the body changes shape — see the mapping below Everything below the base URL is unchanged.
curl -X POST \
https://api.waymatrix.io/v2/matrix/driving-car \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [
[-87.6298, 41.8781],
[-87.6244, 41.8796],
[-87.6722, 41.9227]
],
"metrics": ["duration", "distance"],
"units": "km"
}' Every field, translated
Left is Google's field, quoted from their reference. Right is ours. Where the right-hand side is empty we have no equivalent, which is an answer rather than an omission.
| Theirs | Ours | Notes |
|---|---|---|
origins[].waypoint.location.latLng | locations[] | Google nests an object per origin and names the numbers. We take a flat array of pairs. This is where the order flips — see below. |
destinations[].waypoint.location.latLng | destinations | A different model, and usually less work. We take one locations array and optional sources and destinations arrays holding INDICES into it. A point that is both an origin and a destination is sent once, not twice. |
travelMode: "DRIVE" | the driving-car path segment | A path segment rather than a body field. WALK, BICYCLE, TWO_WHEELER and TRANSIT have no equivalent — we serve driving only. |
routingPreference: TRAFFIC_AWARE | No equivalent | No equivalent, deliberately. We have no live traffic. If this field is why you pay Google, that is a reason to stay and we say so on the comparison page. |
routeModifiers.avoid_ferries | No equivalent | Not supported today. |
X-Goog-Api-Key header | Authorization header | The raw key, with no Bearer prefix. That matches the openrouteservice client libraries, which is the point. |
X-Goog-FieldMask header | metrics | Not the same thing. Google's mask is mandatory and selects response fields; ours is optional and selects which matrices to compute. We return the whole matrix either way. |
response: [] of RouteMatrixElement | durations and distances | The biggest change in your code. Google returns a flat list of elements; we return arrays of arrays, already in the order you sent. |
element.duration ("123s") | durations[i][j] | A string with a trailing s in Google, a number of seconds here. The parse goes away. |
element.distanceMeters | distances[i][j] | Metres by default here too. Send units to change it. |
element.originIndex / destinationIndex | No equivalent | Not needed. Our row and column positions are the positions you sent, so there is nothing to reindex. |
element.status / element.condition | HTTP status, and null in the matrix | A failed pair comes back as null in the matrix rather than as a per-element status. A failed REQUEST is an HTTP error with a code in the body. |
What changes and what does not
Unchanged
- What you are computing. Origins against destinations, durations and distances between every pair. The concept survives the move; only its spelling changes.
- Coordinates are the input. Neither service will geocode for you in this call. If you send Google latitudes and longitudes today, you already have what we need.
- Metres and seconds. Distances are metres and durations are seconds on both sides, once you have parsed Google's duration string.
Different
- The coordinate order flips, and this is the bug you will actually hit. Google names its numbers — latitude and longitude, as an object. We take a two-element array, and it is longitude first, which is what GeoJSON and the openrouteservice clients use. A pair sent the wrong way round is usually still a valid coordinate, so nothing errors; you simply get durations for somewhere in the ocean. Check one result against a road you know before you trust a batch.
- Their response is unordered. Ours is not. Google's own documentation says the elements returned by the stream are not guaranteed to be returned in any order, which is why every element carries originIndex and destinationIndex. Our response is arrays of arrays in the order you sent, so the code that reindexes their output has nothing to do here. Delete it rather than porting it.
- There is no field mask, and that removes a documented trap. Google's guide warns that it is critical to include status in your field mask, because otherwise all messages will appear to be OK. A mask that forgets one field turns failures into silence. We have no mask: you get the whole matrix, and a failure is an HTTP status you cannot miss.
- No live traffic. This is the one that decides it. If a driver is moving right now and a customer is watching an ETA, traffic is the product and we do not have it. Most matrix work is not that — it is scoring options before anyone sets off — but you should be sure which one you are doing.
- The size ceiling goes up, and stops moving. Google caps a call at 625 elements, and at 100 when you ask for its most accurate traffic mode or for transit. Ours is a single number that does not change with the options you pass, and it is large enough that the chunk-and-stitch code most people write to fit 625 becomes unnecessary.
- You may keep what you compute. Google's terms let you cache latitude and longitude for 30 days and do not let you store the durations and distances themselves. Ours are yours: cache them, build tables from them, keep them as long as you like.
Errors
These are read off the live gateway rather than copied from a document. Google returns per-element statuses inside a 200; we do not, so error handling moves from inside the response to around it.
| When | Status | Body |
|---|---|---|
| No key at all | 401 | {"error":"Authorization field missing","code":5001} |
| Key present but rejected | 403 | {"error":"Access to this API has been disallowed","code":5002} |
| Rate limited | 429 | {"error":"Rate Limit Exceeded","code":5003} |
| Monthly quota exhausted | 403 | {"error":"Quota exceeded","code":5004} |
| Request larger than your tier allows | 400 | {"error":{"code":6004,"message":"…Only a total of N routes are allowed."}} |
| Routing engine unreachable | 502 | {"error":"The routing engine is unavailable","code":5008} |
Read off a running gateway on . Google's side was read off their own reference and guide on the date shown, and every field name below is quoted from them rather than remembered. Our side is read from the API description this site generates its samples from. Google changes this API; check theirs before you commit to anything.
- Google — computeRouteMatrix REST reference — read 4 September 2026
- Google — Get a route matrix (request and response examples) — read 4 September 2026
- Google Maps Platform terms of service (caching) — read 2 September 2026
Before you switch traffic
- Flip your coordinates to longitude first, and check one duration against a road you know.
- Collapse origins and destinations into one locations array with index arrays, if your points overlap.
- Delete the code that reindexes elements by originIndex and destinationIndex.
- Delete the code that parses the trailing s off a duration.
- Move error handling from per-element status to the HTTP status around the call.
- Decide honestly whether anything you build needs live traffic.
- Confirm your coordinates are in North America.