Distance matrix
POST /v1/matrix answers “how far is it, by rail, from each of these places to each of those, for
this train?” — the input for freight pricing and network planning. Every cell is the route
POST /v1/route would find for that pair, with the same vehicle rules (electrification, axle load,
gauging, gauge, train protection, maximum speed, reversals, avoided lines and areas). It needs a key
with the route scope.
Request
Section titled “Request”curl -X POST -H "Authorization: Bearer $RAIL_API_KEY" -H "Content-Type: application/json" \ https://api.routing.gleisklar.de/v1/matrix -d '{ "origins": [{ "station": "AH" }, { "station": "AA" }], "destinations": [{ "station": "AK" }, { "station": "AL" }, { "lat": 54.7741, "lon": 9.4367 }], "profile": "freight-electric-de" }'origins,destinations: places as inPOST /v1/route({"station": code},{"lat", "lon"}or{"osm_node"}); at most 1,000 each. Withoutdestinations, the origins are also the destinations.profile,vehicle,avoid,reversal_min,no_reversal: as inPOST /v1/route.async: true: run as a job even if the matrix is small (see below).
Response
Section titled “Response”{ "origins": [{ "name": "Hamburg Hbf", "kind": "station", "ds100": "AH", "lon": 10.0065, "lat": 53.5528, "snapped": { "lon": 10.00667, "lat": 53.55297, "distance_m": 21.4 } }, …], "destinations": [ … ], "durations_s": [[4278, 3174, null], [3960, 2826, null]], "distances_m": [[98412, 70980, null], [92118, 64790, null]], "status": [["ok", "ok", "blocked"], ["ok", "ok", "blocked"]], "summary": { "ok": 4, "blocked": 2, "not_connected": 0, "same_location": 0 }, "elements": 6, "profile": "freight-electric-de", "vehicle": { … }, "data_version": "20261104-0200", "warnings": []}(Illustrative values.) [i][j] is origin i to destination j. Origins and destinations come
back as resolved, with snapped where they meet track the vehicle may use (absent: none within
1.5 km). Without a route the duration and distance are null and status says why:
| Status | Meaning |
|---|---|
ok | A route for this vehicle |
blocked | A route exists, but not for this vehicle (its constraints, what is avoided, or reversing with no_reversal) |
not_connected | No route even ignoring the vehicle |
same_location | Origin and destination are the same place on the network: 0 s, 0 m |
A blocked cell carries no reason: route the pair with POST /v1/route, whose no_route names
the constraint and every blocking stretch.
Durations are the routing cost, in seconds: the time at the line speed capped at the vehicle’s
max_speed_kmh, plus reversal_min per reversal, with service track (sidings, yards) counted
three times — the time the route search minimises. duration_min of POST /v1/route is the
same except that it counts service track once, so a cell whose route uses service track is a
little longer than that route’s duration_min. They have no acceleration and no braking and are not the route’s running time
estimate (see travel time estimates): computing that for up to a
million cells would take milliseconds each. Distances are metres along that route.
Size limits and jobs
Section titled “Size limits and jobs”Elements are origins × destinations. Per request:
| Plan | Elements |
|---|---|
| Free | up to 10,000 |
| Invoiced | up to 1,000,000 |
Larger requests are refused with 413 (too-large). Up to 250,000 elements the matrix is the
answer (200). A larger matrix, or any with "async": true, is answered with 202 and a job:
{ "job_id": "mx_4u3k…", "status": "queued", "progress": 0, "elements": 400000, "poll_url": "/v1/matrix/jobs/mx_4u3k…", "data_version": "20261104-0200", "created_at": "2026-11-04T12:00:00Z" }Poll GET /v1/matrix/jobs/{id} (the poll_url, also in the Location header) every few seconds:
status goes queued → running (progress: share done, 0–1) → done, with the matrix in
result (or failed with error). Sending the same request again while its job exists returns
that job (200) instead of computing it twice. See async jobs
for how long jobs are kept and how many can run.
The JavaScript client polls for you:
const m = await api.matrixResult( { origins, destinations, profile: "freight-electric-de" }, { intervalMs: 2000 },);Metering
Section titled “Metering”A matrix is metered per element (elements), when it is answered or when the job is
accepted; a job that fails later is not refunded. Billing weighs an element as 0.1 requests
(10 elements = 1 billable request; a 50 × 50 matrix bills as 250). Each poll counts as one
request. Failed requests (400, 413, 429) count nothing.