Private by construction
Where your users are going is computed where they are. Once an area is cached there is no request to intercept, log or subpoena.
Turn-by-turn directions, isochrones and time-distance matrices computed on the device. No routing server to run, no per-request bill, and — in offline mode — no coordinates leaving the browser at all.
npm install routewasm
MIT licensed and free to self-host — build tiles from OpenStreetMap yourself and owe us nothing. Development access opens the real hosted datasets for three sessions a day; a subscription buys production capacity and tiles you don't have to build, host or keep current. Built on Valhalla.
Where your users are going is computed where they are. Once an area is cached there is no request to intercept, log or subpoena.
Routing and map rendering keep working with the network off, across reloads. Tiles persist to the browser's origin-private filesystem.
Hosted tiles are served from EU infrastructure. Or point the SDK at your own storage and have no dependency on us at all — the engine is MIT.
import { RouteWasmClient } from 'routewasm';
const client = new RouteWasmClient();
await client.init(); // free Frankfurt tiles, no account
const json = await client.call('route', JSON.stringify({
locations: [{ lat: 50.1069, lon: 8.6638 }, { lat: 50.1214, lon: 8.6567 }],
costing: 'auto',
}));
Requests and responses are
Valhalla's JSON API verbatim —
route, isochrone, sources_to_targets,
trace_route, optimized_route and the rest, across eight costing
profiles. A repeat route in a cached area costs zero bytes and about 33 ms.
Your API key stays on your server. It mints a token that expires in an hour, and the browser only ever holds that — the SDK refreshes it on its own.
// your backend, once per session
app.post('/tiles/auth', async (req, res) => res.json(
await fetch('https://app.routewasm.dev/v1/tile-auth', {
method: 'POST',
headers: { authorization: `Bearer ${process.env.ROUTEWASM_KEY}` },
}).then(r => r.json())
));
// the browser
const mint = () => fetch('/tiles/auth', { method: 'POST' }).then(r => r.json());
await client.init({ tileAuth: await mint(), refreshTileAuth: mint });
Development keys use this exact path too. The only difference is the three-mint daily limit, so an evaluation exercises the same data, authentication and caching as production.
The SDK is free forever. You pay for map data you don't have to build, host or keep current.
€0
€29/month
€99/month
€299/month
A "new user" is someone opening your app for the first time. Once the map data is on their device it stays there, so every later route, matrix and isochrone is free and never reaches us. For comparison, metered APIs charge roughly $2–$5 per thousand requests (Mapbox $2, Google $5, HERE $5 for matrix and isochrone calls).
| Dataset | Source data | Built | Elevation | Size |
|---|---|---|---|---|
| Europe | OpenStreetMap, Geofabrik Europe extract | Yes | 14 GB, 25,073 tiles | |
| Worldwide | OpenStreetMap planet | No | Planet coverage |
Every dataset is served from a dated path, so a refresh never changes what your users are already routing on — you move when you choose to. Cadence is quarterly; say so below if you need it faster.
Being straight about the worldwide set: it was built in February and roads change. Europe is current and has elevation. If your coverage is European, take that one — the worldwide set is for apps that need to route anywhere and can live with data a couple of quarters behind.
A region we don't list, transit agencies ingested, a faster refresh, or tiles delivered to your own storage — tell us what you need and what it's worth to you.
Tiles are fetched on demand and cached. What you pay for is area, not calls: unlocking a city costs tens of megabytes once, and everything after that is free. Measured against the Frankfurt dataset, one user, cold cache:
| What the user does | Downloads | Then |
|---|---|---|
| 20 routes around one metro | 74 MB | 0 bytes, ~33 ms |
| …plus a 10×10 travel-time matrix | +0 MB | |
| …plus 10/20/30-minute isochrones | +115 MB |
Isochrones are the expensive one — they fan out in every direction and pull tiles a route never touches. Everything above is a single allowance; we don't meter calls differently.
Long routes are the exception: past roughly 400 km the cost climbs steeply, so wider requests are rejected up front rather than quietly pulling hundreds of megabytes. Raise or disable that limit when you serve a smaller region. Continent-crossing routes are a job for a server.
The demo routes in your browser, then keeps routing after you turn the network off.