PreviewThis is a preview. The API is not open yet, the addresses in these examples do not serve traffic, and nothing here is a commitment.

WayMatrix
Sign in (not available yet)Get an API key (not available yet)

Migration guide

Moving from openrouteservice

The request does not change. Four behaviours around it do, and this page is about those.

The one line you change is at the top. Everything after it is a behaviour that differs once you have changed it — the error shapes your retry logic reads, the endpoints that do not exist here, and two traps in the Python client.

The change

client.py

Removed: - base_url = "https://api.openrouteservice.org"
Added: + base_url = "https://api.waymatrix.io"  headers   = {"Authorization": API_KEY}

Everything below the base URL is unchanged.

matrix.shgenerated from the API description
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"
  }'

What changes and what does not

Unchanged

  • The paths. Matrix, directions and isochrones sit at the same paths under /v2, with the same profile segment.
  • The auth header. The raw key in an Authorization header with no Bearer prefix, which is what the official client libraries send. A Bearer prefix is tolerated if you have one, and api_key as a query parameter works too.
  • The request bodies. Locations, sources, destinations, metrics, units. Nothing is renamed and nothing new is required.
  • The response shapes. Durations and distances as arrays of arrays, with a metadata block. Fields are added, never removed or renamed, so a parser written against the original does not notice us.
  • The attribution. The OpenStreetMap credit rides along in the response metadata exactly as before. Stripping it would breach the data licence, so a test in the gateway asserts it survives every response.

Different

  • North America, and driving only. One continent, one profile. Any other profile name returns the engine's own error, the same one you would get from a graph that does not carry it.
  • Three endpoints, not nine. No geocoding, no places, no elevation, no optimization. If your application calls those, you are splitting it across two providers rather than moving it. A call to one of them returns a clean 404 in the same error shape as everything else, so it fails where you can see it rather than in a parser.
  • We never return 503. This one will change your retry logic. An unreachable engine surfaces as 502, deliberately, because the Python client retries 503 automatically and a retry storm during an outage is worse than a clean failure. 504 stays 504 for the same reason.
  • Rate limiting is 429 and quota exhaustion is 403. The distinction matters because the Python client backs off and retries a 429 and treats a 403 as terminal. That is the behaviour we want in both cases.
  • The per-request ceiling is larger. Requests over your tier's ceiling are refused before any work happens, with the engine's own error code and message rather than a truncated result.

Errors

Every row below was read off the live gateway rather than copied from a document. The first four are byte-compatible with what hosted openrouteservice sends, so an existing error handler keeps working. Two rows were re-read against the running gateway on 8 September 2026 and match byte for byte: the 401 and the 404. The other five need either a key or an outage to provoke, so they still rest on the 4 September reading — which is why the date below has not moved.

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}
A path we do not serve 404 {"error":"Not found","code":5010}

Read off a running gateway on . The rejection shapes below were read off the live gateway on the date shown, not copied from a specification. A successful keyed request is verified too: the gateway's acceptance gate runs against the live service and asserts a 3x3 matrix, its element count and the attribution. That run proves it reached the configured gateway rather than production specifically, because the target is held as a secret.

If you use the official client libraries

Two things that will bite

  • Do not request GPX through the Python client. Every released version calls response.json() on the body unconditionally, so a GPX response raises an HTTP error with a 200 status. This is not a difference between us — it fails the same way against hosted openrouteservice — but it looks like our fault. Use plain HTTP or the JavaScript client for GPX.
  • The exception classes carry different attributes. ApiError has status and message; HTTPError has status_code. Reading status_code off both returns nothing rather than raising, which turns a failed call into a silently wrong result.

Before you switch traffic

  1. Change the base URL, and nothing else about the request.
  2. Confirm you only use matrix, directions and isochrones.
  3. Confirm your coordinates are in North America and your profile is driving.
  4. Find every retry on 503 and add 502, or move to retrying on 429 only.
  5. Run one real request and compare the durations against what you were getting before.
Get a free API key (not available yet) See the pricing