50,000 free calls a month, card-free. Get an API key →

news / matrix-api · raw .md
Routing7 min readMapMap engineering
A grid of origin and destination points with measurement lines drawn between every pair, forming a dense woven lattice with one block delineated.

POST /matrix bills a call per started block of 25 elements

POST /matrix returns the road travel time and distance between every source and every target, as two row-major arrays: durations in seconds, distances in metres, null where a pair is unreachable. It bills one call per started block of 25 elements, where an element is one sources × targets pair, capped at 10,000 elements per request. A 12 by 40 dispatch matrix is 480 elements, so 20 blocks, so 20 billable calls: 1p at the standard rate, 20p with truck costing. The same computation runs inside POST /optimise, which is why truck dimensions and ADR constraints shape a fleet plan rather than getting checked after it.

What does POST /matrix return?

Two arrays, and nothing else. sources are the rows, targets are the columns, and durations[i][j] is the time from sources[i] to targets[j]. Locations are {"lat": …, "lon": …} objects with named keys, so there is no coordinate order to get wrong.

sh
export BASE=https://api.mapmap.ai
export API_KEY=snk_...

curl -fsS -X POST "$BASE/matrix" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sources": [{ "lat": 51.5074, "lon": -0.1278 }],
  "targets": [
    { "lat": 52.4862, "lon": -1.8904 },
    { "lat": 53.4808, "lon": -2.2426 }
  ],
  "costing": "truck",
  "costing_options": { "truck": { "height": 4.0, "weight": 40.0 } }
}'

The response below is not truncated. That is the entire shape:

json
{
  "durations": [[9000.0, 12500.0]],
  "distances": [[171100.0, 262000.0]]
}

One depot and two targets gives one row of two. Because that request carried truck costing with a 4 m height and a 40 t weight, the durations are times a 40-tonne lorry can achieve on roads it is allowed to use, not car times with a margin added.

FieldRequiredNotes
sourcesyesRow locations (origins), array of {lat, lon}
targetsyesColumn locations (destinations)
costingyesauto, truck, bus, motor_scooter, motorcycle, bicycle or pedestrian
costing_optionsnoPassed to the engine verbatim, keyed by costing name
unitsnoAccepted, then ignored: distances always come back in metres
idnoOpaque identifier, echoed back

Unreachable pairs come back as an honest null. We do not substitute a large number and let you discover the difference in production.

How much does a matrix request cost?

Multiply blocks by your class rate. Elements are sources × targets. Blocks are elements divided by 25 and rounded up, with a floor of one block. The class is Standard at 0.05p normally, and Premium at 1p when the body carries "costing": "truck", selected automatically per request.

MatrixElementsBilled callsStandardPremium (truck)
1 × 1110.05p1p
5 × 52510.05p1p
5 × 63020.1p2p
12 × 40480201p20p
100 × 10010,00040020p£4

Read the 5 × 6 row twice, because it is the one that surprises people. A 30-element matrix is one full block plus one started block, and a started block bills as a whole block. Adding a single target to a 5 × 5 matrix doubles its price. The effect fades at scale (480 elements rounds 19.2 blocks up to 20, an overshoot of about 4%), but it is real at the bottom end and we would rather you read it here than infer it from an invoice.

Those prices are the entry band. Bands are marginal and selected by month-to-date usage, and every block counts as one call against them, so a single 100 × 100 matrix advances you through the bands exactly as far as 400 route requests would. Standard falls to 0.03p beyond 1M calls a month and 0.02p beyond 10M. Premium falls to 0.7p and 0.4p across the same bands. Prices reconcile with /pricing.json, which wins if this page ever drifts.

On the free tier, the arithmetic runs through the premium weight. The 50,000 included monthly calls are denominated in standard calls, and one premium call draws 20 of them. So the 12 by 40 truck matrix above is 20 premium calls, which draws 400 included calls, and the free tier absorbs 125 of those requests a month. The same matrix with "costing": "auto" draws 20 included calls, and the free tier absorbs 2,500 of them.

Billing precision. The gateway meters an internal millipence ledger at 1p = 1,000 millipence, so fractional pence bill exactly rather than rounding up per call. A one-block standard matrix debits 50 millipence. GET /v1/keys/self returns credits_pence (floored) and credits_millipence (full precision), which is why a single small matrix can move your balance without moving the pence figure. Pay-as-you-go is prepaid credit, and the monthly plans bill a flat fee with overage falling back to that same credit, so nothing is ever post-paid. With no credit you get a 402 or a 429, never a surprise invoice.

What happens if you ask for more than 10,000 elements?

A 400, raised before any metering or debit, so an oversized matrix costs you nothing. The gateway multiplies sources by targets in the auth layer and rejects the request there:

ini
matrix too large: 150 sources × 150 targets = 22500 elements exceeds the
10000-element limit; split the request

The 10,000-element ceiling is a 100 × 100 matrix, and 400 billed calls is the most a single /matrix request can ever cost. Split larger problems by source block.

Why does /optimise compute its matrix through our engine?

Because a solver left to do its own routing would plan legs a lorry cannot drive. POST /optimise (alias /optimize) never delegates routing to the solver sidecar. The gateway computes the full duration and distance matrix through its own engine first, with your costing, costing_options and adr profile applied, then hands the solver explicit matrices. The optimisation therefore respects exactly the same constraints as /route and /matrix: a bridge too low for your trailer is not a cheap edge the solver can be tempted by, because the matrix cell that crosses it already reflects the detour.

The billing follows a different rule, and it favours you. One optimisation request bills a flat 10 calls of its class regardless of problem size, and the internal matrix is not billed separately. That is 10p for a truck plan and 0.5p for a car plan, whether the problem has five stops or the fair-use ceiling of 200 unique locations. A 200-location plan implies a 200 × 200 internal matrix, which is 40,000 elements, four times more than you may request directly from /matrix, still for a flat 10 calls. If you want a plan, ask for a plan. Call /matrix directly when you want the raw numbers for your own solver or your own ranking logic.

Two differences worth holding in mind. Unreachable pairs are null on /matrix and a large sentinel cost inside /optimise, so an absurd duration in a plan means a stop is probably unreachable rather than distant. And /optimise is enabled per deployment: with no solver sidecar configured it returns 503 with problem type urn:sn-gateway:problem:optimisation-not-enabled. We wrote the solver up in full in route optimisation with truck and ADR constraints and in the optimisation guide.

What POST /matrix does not do

It applies no ADR restrictions, and it charges you premium for asking. This is the trap and it is worth the bold type. The adr extension is honoured only on POST /route and POST /optimise. Send a top-level adr object to /matrix and it is ignored, but the pricing classifier still sees it and bills the whole request as Premium, at 20 times the standard rate, for a matrix with no ADR enforcement in it. Truck dimensions via costing_options.truck do shape a matrix. Tunnel-category compliance does not. Do not build a dangerous-goods step on this endpoint.

It returns no geometry. A matrix is numbers, not lines. Fetch each leg from POST /route when you need something to draw.

units is accepted and then ignored. The gateway normalises response distances to metres whatever you pass. We would rather one unit in the response than a field that silently changes what your integers mean.

Try it

The analysis guide carries the full parameter and error tables for /matrix alongside the other three analysis endpoints, and isochrones, priced per contour covers the reachability side of the same family. A key comes from one card-free call to POST /v1/keys and works immediately. Agents can reach the same capability as the matrix tool on our MCP server.

Routing derives from OpenStreetMap, so credit "© OpenStreetMap contributors" when you render or republish it.