Migration guide
Moving off api.openrouteservice.org before November
WayMatrix is a hosted routing API — matrices, directions and isochrones — that answers the Openrouteservice requests you already send.
Same request shapes, same auth header, our own road graph behind them. The rest of this page is what moving actually involves.
Get a free API keyYour requests don't change. A few behaviours around them do, and this page covers those — whether you are planning the move or your calls have already started failing.
Change the base URL at the top. The rest of this page covers what behaves differently after that: error handling, the endpoints we don’t have, and two problems in the Python client.
Get a key before you switch rather than during: sign in, create one, and you have it in a minute. There is no waiting on a person any more.
My Openrouteservice calls started failing
- The old host has not been switched off yet. On 1 October 2026 api.openrouteservice.org was still serving, and every documented API path under it still authenticated: an unauthenticated matrix, directions or isochrones request answered 401, not 404. They have said it shuts down after 2 November 2026. So a failure today is much more likely to be your allowance or your key than the shutdown.
- The reduced quota is the live cause, and it refuses differently. Openrouteservice announced a cut to 10% of plan quota on the deprecated host from 27 August 2026, which is the change most applications noticed. Their limits refuse with 429 for the per-minute cap and 403 for the daily one. Neither is the shutdown, and both clear on their own — so the base URL is still the thing to change, but you have until their November window to change it.
One thing to settle before you change anything
Openrouteservice serves nine endpoint families. We serve three — matrix, directions and isochrones. Geocoding is on our development plan; the rest are not, and route optimisation never will be here. So if your application calls one of the others, it cannot move here whole today, and no amount of the advice below changes that. Settle it before anything else: it is the only question here that can end a migration rather than adjust it.
-
Geocoding and place search.
Their /geocode endpoints are Pelias, and /pois is theirs as well. We serve neither today. Geocoding is on our development plan rather than something we have ruled out — but a plan is not a date, so plan your migration as though you will need addresses resolved somewhere else, and treat ours as a convenience if it arrives first. Until then that half of your application stays with them or moves to a third provider.
-
Elevation and route optimisation.
Elevation we do not serve and have not planned. Route optimisation is a different product rather than a missing endpoint — a planner you feed a fleet and a set of stops, not a base URL you swap — and it will not appear here. It is Optimiciti, the route planner this API sits under, and that is where to go for it.
Calling any of them here returns 404 in the usual error format, so you would find out on the first request rather than in production.
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.
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. Send the raw key in an Authorization header with no Bearer prefix, as the official client libraries do. A Bearer prefix works too. An api_key query parameter also works, as it does on Openrouteservice, but prefer the header: a key in a URL ends up in server access logs, ours and any proxy's in between.
- The request bodies. The fields are the same: locations, sources, destinations, metrics and units. Nothing is renamed and nothing new is required.
- The response shapes. Durations and distances come back as arrays of arrays, with a metadata block. Fields are added, never removed or renamed, so your existing parser keeps working.
- The attribution. The OpenStreetMap credit is in the response metadata, as before. We never remove it: the data licence requires it.
- Multi-band isochrones. An isochrone request may carry up to ten intervals, as it may on Openrouteservice, so a call asking for ten, twenty and thirty minutes in one request moves across unchanged. Checked on 8 October 2026 against this service: ten bands from one request, and a single-band request unaffected.
Different
- North America, and driving only. One continent, one profile. Any other profile name returns an error, the same one Openrouteservice returns for a profile it doesn't have.
- We never return 503. This one changes your retry logic. If routing is unreachable you get 502 (bad gateway), not 503. The Python client retries 503 automatically, and a retry storm during an outage makes it worse. 504 (timeout) stays 504 for the same reason.
- Rate limiting is 429 and quota exhaustion is 403. The Python client backs off and retries a 429 (too many requests) and treats a 403 (allowance used up) as final. This is the right behaviour in both cases.
- The per-request ceiling is larger. A request over your plan's limit is refused before any work happens, with an error code and message, not a truncated result.
- Avoiding an area, or using dynamic weights, caps the route at 100 km. An ordinary route can be long: Chicago to Denver, 1,613 km, works. Add avoid_polygons and the same route is refused with "With avoid areas, the approximated route distance must not be greater than 100000.0 meters". Dynamic weights have the same limit. Both make a route much slower to compute, which is why the limit is lower.
- Some limit errors come from us, in Openrouteservice's format. When a request is over your plan's limit, we refuse it with the same error code and message Openrouteservice uses, so your error handling keeps working. Oversized matrix requests are the common case, and the number in the message is your plan's limit. Read the message to see what was exceeded. Don't rely on the info.engine section of an error response to tell whether we or the routing engine refused it: that may change.
- A flexible matrix can run out of searching, and the status will mislead your retry code. A matrix carrying those options searches the road network once per source, and a single request may only search so far. One that reaches the ceiling fails with `500` and code `6020`, partway through, and you are not charged for it. The trap is the status: the Python client retries `5xx` automatically, so retry logic you already have will fire — and the same request will reach the same ceiling again, having done the same work twice. Send fewer sources, or cover a smaller area. This is not a plan limit, so a larger plan does not lift it.
- A 403 does not always mean your key is wrong. Two refusals share one message. `{"error":"Access to this API has been disallowed"}` is what a bad key gets, code `5002`, and it is also what the free plan gets for sending an `options` block on a matrix, code `5011`. The text is identical; only the code differs. Branch on the code: `5002` means fix the key, `5011` means your plan may not send that field, and retrying either is pointless.
- Two different refusals look alike when you avoid an area. Both return 400. Code 2004 covers the 100 km distance limit above, and also a route with more than 50 waypoints (the points a route must pass through). Only the message tells those two apart, so a client that checks the code alone might tell someone to shorten their route when the fix was to send fewer waypoints. Code 2003 is a separate limit on the size of the avoided polygon: "The area of a polygon to avoid must not exceed 2.0E8 square meters". A polygon about a degree across hits it even on a short route. Read the message, not just the code.
Errors
Every row below was read from the live service. The first four match what Openrouteservice's public API sends exactly, so an existing error handler keeps working.
| 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."}} |
| More than 50 waypoints on a route | 400 | {"error":{"code":2004,"message":"…The specified number of waypoints must not be greater than 50."}} |
| Your plan may not send that field checked | 403 | {"error":"Access to this API has been disallowed","code":5011} |
| 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 error responses below were read from the live service on the date shown, not copied from a specification. Separately, on 11 September 2026 we ran openrouteservice-py's own client, unmodified except for the base URL and key, against https://api.waymatrix.io, and all 24 checks passed: matrix, directions in three formats, isochrones, and six error responses. That covers the client's own quickstart, not every Openrouteservice client or parameter. It is a dated check, not a standing guarantee.
- Openrouteservice — deprecating api.openrouteservice.org in favour of api.heigit.org — read 1 October 2026
- Openrouteservice — reducing the quota of the deprecated API — read 1 October 2026
- Openrouteservice — new web client, shutting down old clients — read 19 October 2026
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 response, so a GPX response raises an error even though the request succeeded (200). It fails the same way against Openrouteservice's public API. 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 on an ApiError returns nothing instead of raising, so a failed call can look like a wrong result.
Before you switch traffic
- Change the base URL, and nothing else about the request.
- Confirm you only use matrix, directions and isochrones.
- Confirm your coordinates are in North America and your profile is driving.
- Find every retry on 503 and add 502, or move to retrying on 429 only.
- Run one real request and compare the durations against what you were getting before.