Skip to content
Documentation menu
docs / mcp-tools · raw .md

MCP tool reference

Every tool the hosted MapMap MCP server serves, with its inputs, its output and one real call. This page is generated from the server's own tools/list (input and output JSON Schemas built from the server's types), captured from https://mcp.mapmap.ai/mcp on 2026-11-06 (sn-mcp 0.3.0, 56 tools). Do not edit it by hand: run node --experimental-strip-types scripts/generate-mcp-tools-md.mjs in website/.

How to connect, keys and billing, resources, prompts and self-hosting are on MCP server. Recipes per assistant are on Use MapMap from any AI assistant.

Reading this page

  • Access says how the tool can be called on the hosted endpoint. Keyless tools answer without a key under fair use; present your own snk_ key as Authorization: Bearer snk_… and their metered calls bill to you instead. Keyless, local tools are pure computation: no network call, never metered. Your own key tools refuse keyless calls on the hosted endpoint. Operator tool means it refuses on the hosted endpoint.
  • Inputs lists every argument the server accepts. Default is the schema's own default or the default the server's description states; empty means none is stated. Limits are the bounds the schema enforces; limits stated only in prose are in the description.
  • A coordinate (LatLon) is a {lat, lon} object (preferred) or a GeoJSON [lon, lat] array, longitude first. Distances are metres and durations seconds unless a field name says otherwise.
  • Caveats are the server's own sentences about what an answer does and does not mean, quoted from its description. Pass them on when you relay the answer.
  • Example calls were made keyless on the hosted endpoint on the date shown. Results are trimmed for length: long strings end in … and arrays show their first two entries.
  • Tool failures come back as MCP tool errors (isError: true) with a message naming the fix, so an agent can correct itself.

Tools by group

Routing

Routes in every costing, what a route passes, reachability, travel matrices, elevation and trace matching.

route

Compute a turn-by-turn route between origin and destination (optionally via waypoints). Costing "auto" = car, "truck" = lorry, "bicycle", "pedestrian" = walking, "motor_scooter" = moped.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • A wheelchair route through the MapMap gateway returns a grade block (maximum grade, ascent and descent sampled from a 30 m elevation model, the use_hills preference sent, and cap.enforced for a max_grade_pct, which the engine accepts but does not enforce yet: say so) and, where the deployment has a surface graph, a surface_quality block: applied: true means ways rated smoothness bad or worse in OpenStreetMap were avoided in the named region, a preference from ratings where they exist that says nothing about kerbs, widths, steps or inclines; applied: false gives the reason.
  • Relay both caveats and never describe such a route as suitable for every user; bicycle {bicycle_type, use_roads 0-1, use_living_streets 0-1, avoid_bad_surfaces 0-1, use_hills 0-1} for quiet-ride and surface preferences.
  • Any of truck, auto, bicycle, pedestrian or motor_scooter routes may set rationale: true (opt-in, costs up to 1 + N extra routing calls) to learn which declared truck constraints or avoidance-side preferences (hills, surfaces, tolls, unlit streets, …) actually changed the route (rationale.avoided[], basis route_divergence, it proves a field was binding, it does not identify the physical restriction or feature, and no live traffic or incident data is ever attributed).
  • ADR honesty: applied_adr.forbidden_tunnel_categories describes the LOAD, not the returned route, and applied_adr.tunnel_enforcement states the boundary: roads are excluded only where the routing graph records an ADR tunnel category, so an unchanged route is not a clearance.
  • Set landmarks: true for turn instructions anchored to recognisable places, each manoeuvre that passes one gains a landmark_instruction like "Turn right just after the Shell garage" beside the engine's own street-name instruction, which is never replaced.
  • Nothing is named unless it is recognisable from the road, within 40 m of the junction and not tagged as closed, so many routes return none and a landmarks.annotated of 0 with no note means this route genuinely passes nothing recognisable.
  • Through the gateway each manoeuvre also carries the road it starts on: country, subdivision, ref (route numbers only), road_class, speed_limit_unit (mph or km/h, from the road's country), speed_limit_sign (vienna or mutcd), speed_limit_posted (the limit as the sign shows it, in the road's unit) and driving_side; each is absent when the map does not know it, never guessed.
  • Optional avoid and exclude name road features to keep off, and the difference between them is not cosmetic.
  • avoid ("tolls", "highways", "ferries") is a PREFERENCE: it sets the costing's willingness to zero, and the engine's own reference says that is not guaranteed to avoid the feature, measured, avoiding tolls across the Dartford Crossing returns the same tolled route, because the untolled alternative is fifty kilometres further.
  • exclude ("tolls", "highways", "ferries", "bridges", "tunnels") is a hard exclusion: it can answer NO ROUTE rather than a detour, and it depends on the routing engine's own hard-exclusion setting, which the engine does not report and this server cannot read, so it is requested, never promised.
  • "tolls" and "highways" under avoid exist only on motorised costings; asking for one on a bicycle is refused rather than silently ignored.
  • Whatever is applied comes back in avoidance, with the caveats, relay them, because "avoid tolls" read as a guarantee is the failure mode here.
  • It is a snapping preference, not a manoeuvre guarantee, and it needs a coordinate genuinely offset from the road centreline; street_side_tolerance_m and street_side_max_distance_m bound the window in which it applies, and a pair leaving no window is refused rather than answered with the preference silently inert.
  • Without it a zone can only be named, never priced.
  • Set scenic: true (auto costing only) to ask whether there is a prettier way.
  • It is a PEER OFFER, never a substitution: the route in geometry_polyline6 is byte-for-byte what the same request returns without the flag, and the prettier way, when there is one, arrives beside it in scenic, with its own geometry in scenic.alternative_geometry_polyline6 and what it costs in scenic.spoken ("About seven minutes longer than the quick way").
  • That is an ANSWER, not a failure, and it is the one to relay.
  • It is a re-ranking of routes the engine already proposed, not a scenic search: a beautiful road the engine never offered was never considered, and scenic.caveats says so.
  • Like rationale, IT COSTS EXTRA METERED COMPUTATIONS, at most two, which is why it is opt-in and never on by default.
  • available: false with a note means this deployment could not look, which is not the same claim as nothing being there.

Inputs

InputTypeRequiredDefaultLimitsDescription
destinationRouteLocationyesRoute destination.
originRouteLocationyesRoute origin.
avoidAvoidFeature[]noRoad features to avoid as a PREFERENCE. Rendered as the costing's willingness factor set to zero, a thumb on the scale, not a ban. The engine's own reference is explicit that a value of zero is not guaranteed to avoid the feature entirely, and it does not: asking to avoid tolls across the Dartford Crossing returns the same tolled route, because the untolled alternative is fifty kilometres further. Use exclude when you mean a ban.
bicycleBicycleSpecnoBicycle options (bicycle type, road/surface/hill preferences). Requires costing "bicycle".
costingCostingKindno"auto"Costing model: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
emissionsEmissionsSpecnoThe vehicle's emission declaration, for UK clean-air / low-emission zone assessment. Given, the response's zones block names every zone the route enters and what THIS vehicle pays there, with the publishing authority cited. Without it a zone can only be named, never priced. Needs the MapMap gateway, which holds the curated zone dataset; the full dataset is readable at GET /v1/zones.
excludeExcludeFeature[]noRoad features to exclude outright, rendered as the costing's hard exclusion flag. Two things follow from that and both matter: a hard exclusion can return NO ROUTE rather than a detour (excluding tunnels on a Rotherhithe crossing has no answer), and the flags depend on the routing engine's allow_hard_exclusions setting, which the engine does not expose and nothing here can read, so this is never promised, only requested.
exclude_polygonsnumber[][][]noAreas to avoid, scenario analysis ("close this bridge and re-route"): an array of polygons, each an array of [lon, lat] pairs forming one exterior ring (GeoJSON-style, longitude FIRST). Roads intersecting any ring are excluded from the search. When no route avoids them the answer is "no path": the search is never widened to snap a stop onto a road beyond the area. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.
landmarksbooleannofalseName landmarks in the turn instructions (default false): each manoeuvre that passes a recognisable place, a petrol station, a supermarket, a household-name chain, gains a landmark_instruction like "Turn right just after the Shell garage" beside the engine's own street-name instruction, which is never replaced. Nothing is named unless it is recognisable from the road, within 40 m of the junction and not tagged as closed, so many routes come back with none: a wrong landmark is worse than no landmark. Needs the MapMap gateway, whose place index does the lookup.
pedestrianPedestrianSpecnoPedestrian options (lit-street preference, wheelchair/blind type, hiking difficulty). Requires costing "pedestrian".
rationalebooleannofalseExplain the route (default false): re-routes with each declared truck constraint (truck costing) or avoidance-side routing preference (auto, bicycle, pedestrian, motor_scooter) relaxed and reports the ones that actually changed the route as rationale.avoided[]. Opt-in, it costs up to 1 + N extra routing calls, one per declared field plus one combined probe, and it is billed for the ones it actually makes: at most 8 in total, typically fewer, and 1 when there is nothing to probe. Against the hosted gateway each probe is its own metered route call, which is exactly what POST /route with rationale: true charges for its own fan-out, so the two surfaces price the same explanation the same way.
scenicbooleannofalse, auto costing onlyAsk whether there is a prettier way (default false, auto costing only). The route in geometry_polyline6 is NEVER swapped: what comes back is a peer offer beside it, in scenic, carrying the plain sentence that justifies it. No sentence, no offer: a route that scores well but cannot support one is dropped rather than dressed up, and scenic.reason is then null, which is an answer. Every candidate that lost says why in scenic.rejections[] (same_route, no_scenic_gain, not_scenic_enough, over_time_budget, nothing_to_say), so "no prettier way" is always attributable. Opt-in because it costs: at most two extra metered route computations, the same billing shape as rationale. Needs the MapMap gateway, whose basemap archive the scoring is measured against.
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; when present, ADR dangerous-goods costing options are merged into the request.
waypointsRouteLocation[]noOptional intermediate stops, visited in order between origin and destination.

AvoidFeature: A road feature to avoid as a PREFERENCE, not a ban. "tolls" and "highways" apply to motorised costings only (auto, truck, bus, motor_scooter, motorcycle); "ferries" applies to every costing. Asking for one on a costing whose engine table has no field for it is refused rather than silently ignored.

One of "tolls", "highways", "ferries".

BicycleSpec: Bicycle options for costing "bicycle": the bicycle type plus road, surface and hill preference weights, mapped onto Valhalla costing_options.bicycle.

InputTypeRequiredDefaultLimitsDescription
avoid_bad_surfacesnumbernoAvoidance of surfaces unsuited to the bicycle type, 0.0–1.0 (1.0 = strictly avoid bad surfaces).
bicycle_typestringnospeed and surface toleranceBicycle type: "road", "hybrid" (default), "city", "cross" or "mountain". Sets default speed and surface tolerance.
use_hillsnumbernoWillingness to take hills, 0.0–1.0 (0.0 = avoid climbs even at the cost of longer routes).
use_living_streetsnumbernoPreference for living/shared streets, 0.0–1.0.
use_roadsnumbernoWillingness to ride roads alongside motor traffic, 0.0–1.0 (0.0 = prefer cycleways and quiet streets, the quiet-ride slider).

RouteLocation: One location of a route request: a coordinate, plus the optional kerbside approach for arriving at it. A bare {lat, lon} is still a complete location, every kerbside field is optional and omitting all of them is exactly the request that was made before they existed.

InputTypeRequiredDefaultLimitsDescription
preferred_sidePreferredSideKindnoWhich side of the street to arrive on (or depart from). same (or curb) puts the vehicle on the door's side of the road, resolved against the locale's driving side. Two honest limits: this is a snapping preference, not a manoeuvre guarantee, the engine prefers an edge on that side, it does not promise the driver never crosses, and it needs a coordinate genuinely offset from the road centreline, because a point on the centreline has no side.
street_side_max_distance_mintegerno1000 mMetres: further than this from the road centreline, the side of street is treated as none and preferred_side does nothing. Engine default 1000 m. Together with street_side_tolerance_m this is a WINDOW: a pair that leaves no window (tolerance at or above max distance) is refused here rather than answered with a route on which the kerbside preference was silently inert.
street_side_tolerance_mintegerno5 mMetres: nearer than this to the road centreline, the side of street is treated as none and preferred_side does nothing. Engine default 5 m.

PreferredSideKind: Side-of-street preference for arriving at or departing from a location. Carries both vocabularies: MapMap's own same/opposite/either and the OSRM/Mapbox approaches words curb/unrestricted, which are aliases for same and either. Both spell the same request, on this surface and on the HTTP API.

ValueMeaning
"same"The side the location itself projects to, the kerb, resolved against the locale's driving side (the left kerb in the UK, the right in Germany).
"opposite"The far side of the road from the location.
"either"No side preference.
"curb"Alias for same, from the OSRM/Mapbox approaches vocabulary.
"unrestricted"Alias for either, from the OSRM/Mapbox approaches vocabulary.

ExcludeFeature: A road feature to exclude outright. A hard exclusion can leave a request with no path at all (that is the honest answer, not a failure) and it depends on the routing engine's own hard-exclusion setting, which nothing here can read.

One of "tolls", "highways", "ferries", "bridges", "tunnels".

PedestrianSpec: Pedestrian options for costing "pedestrian": lit-street preference, pedestrian type, hills, surface quality and hiking difficulty, mapped onto Valhalla costing_options.pedestrian (surface quality and the grade cap are MapMap gateway fields). All preferences, never guarantees: the router prefers matching ways where the map data supports it.

InputTypeRequiredDefaultLimitsDescription
max_grade_pctintegernoMaximum grade in whole percent, 1–90. Needs the MapMap gateway: the routing engine accepts this cap but does not enforce it yet, and the gateway's grade.cap says so on every route that sets it.
max_hiking_difficultyintegerno1Maximum hiking-trail difficulty the route may use, as OSM sac_scale 1–6 (1 = well-cleared, mostly flat trails; 6 = demanding alpine terrain). Default 1.
surface_qualitystringnowhere the deployment has a surface graphSurface quality for wheelchair routes: "avoid" (the default where the deployment has a surface graph covering the route) or "ignore" (use the ordinary graph). Wheelchair only, and needs the MapMap gateway; the surface_quality block in the output says whether it was applied.
typestringnoon-foot profilePedestrian type: "wheelchair" (avoids mapped steps and kerbs and the rougher surface classes; hills per use_hills) or "blind" (richer guidance detail). Omit for the default on-foot profile.
use_hillsnumberno0Willingness to take hills, 0.0–1.0 (0.0 = avoid climbs as strongly as the engine can; the engine default is 0.5). A preference, not a cap. The MapMap gateway sends 0.0 on a wheelchair route that does not set it, and reports that in grade.preference.
use_litnumbernoPreference for lit streets, 0.0–1.0 (1.0 = prefer lit paths as strongly as possible, the "walk me home on lit streets" option). Unlit ways are still used when no lit alternative exists.

Output

FieldTypeAlways presentDescription
distance_mnumberyesTotal route distance in metres.
duration_snumberyesTotal estimated travel time in seconds.
geometry_polyline6stringyesFull route geometry as a Google encoded polyline with six digits of decimal precision (polyline6).
maneuversManeuverOut[]yesOrdered turn-by-turn maneuvers across all legs.
summarystringyesOne-line human-readable summary of the route.
applied_adrAppliedAdrnoThe ADR costing merged into the request, or null when no truck profile was given.
avoidanceAppliedAvoidancenoWhat avoid/exclude became, and the caveats that go with it (only when either list carried something).
gradeany JSONnoThe hill report on a pedestrian route (only through the MapMap gateway): maximum grade, total ascent and descent sampled along the route from the elevation model, the hill preference sent, and whether a requested grade cap was enforced. Relay cap.enforced and the caveat as given.
landmarksLandmarkSummarynoThe landmark-annotation summary (only when landmarks: true was requested): how many manoeuvres gained a landmark_instruction, and a note when the per-route cap was hit or the deployment has no place index. A zero with no note means this route genuinely passes nothing recognisable.
rationaleRouteRationalenoWhy the route goes this way (only when rationale: true was requested; computed for truck, auto, bicycle, pedestrian and motor_scooter costings).
scenicScenicOffernoThe scenic peer offer (only when scenic: true was requested): whether there is a prettier way, the sentence that justifies it, what the detour costs, and why every candidate that lost lost. The route above is never swapped for it.
surface_qualityany JSONnoSurface quality on a wheelchair route (only through the MapMap gateway): applied true with the region, the exact rule, the data timestamp and a caveat when the route was computed on the surface graph; applied false with a reason otherwise. Relay the caveat: it is a preference from OpenStreetMap ratings where they exist, and says nothing about kerbs, widths, steps or inclines.
view_urlstringnoA mapmap.ai link that draws this route on a map: open it or pass it on as the last line of the answer. Nothing is stored server-side and opening it needs no key; the link itself carries the stop coordinates and stop names (plus any arrival clocks, dwell times and totals) and nothing else, so anyone holding it can read those stops. Absent when the plan cannot be linked (see view_url_note) or the server runs with links turned off (SN_MCP_SITE_URL empty).
view_url_notestringnoWhy there is no view_url (too many vehicles or stops for one link, or over the 6,000 character cap), or what the link left out to fit under the cap (arrival times, dwell times, then stop names). Relay it with the link.
zonesany JSONnoClean-air / low-emission zones this route enters and what the declared vehicle pays in each, with the publishing authority cited (only when emissions was declared). Passed through verbatim from the gateway's curated dataset, the same figures GET /v1/zones publishes, so a charge can always be audited back to its source.

AppliedAdr: The ADR costing that was merged into a truck route request. Every field here describes the request: the declared load and the costing options built from it. Nothing here is an assertion about the returned geometry, see TunnelEnforcement.

FieldTypeAlways presentDescription
costing_optionsany JSONyesThe exact Valhalla costing_options JSON merged into the request.
forbidden_tunnel_categoriesstring[]yesADR tunnel categories the vehicle is forbidden from under the worst-case reading of ADR 8.6.4 (conditional clauses assumed to apply). Empty when unrestricted. This is not a claim that the returned route contains no tunnel of these categories, read tunnel_enforcement for what was actually applied.
hazmatbooleanyesWhether the profile declared dangerous goods.
tunnel_enforcementTunnelEnforcementyesWhat the router did with forbidden_tunnel_categories, and the boundary of the resulting guarantee.
tunnel_codestringnoCanonical ADR tunnel restriction code applied ("B/D", "(-)", …), or null when no code was declared.

TunnelEnforcement: How the declared ADR tunnel code reaches the routing engine, and what a returned route therefore does (and does not) prove. This block exists because forbidden_tunnel_categories is a statement about the load (ADR 8.6.4 applied to the declared code), and on its own it reads like a statement about the route. It is not one. The engine excludes a road only where the routing graph records an ADR tunnel category for it, and that category is derived from OSM hazmat:* tagging. Coverage is therefore uneven: a corridor with no such tagging is not excluded and looks, in the response, exactly like a corridor that was checked and cleared. The field is emitted on every applied_adr, so a caller can never receive the categories without the boundary that qualifies them.

FieldTypeAlways presentDescription
basisstringyesHow the code is applied. "graph_adr_tunnel_category": merged into costing_options.truck.adr_tunnel_code and matched, during the search, against each road's ADR tunnel category as recorded in the routing graph.
caveatstringyesWhat an unchanged route does and does not prove, in one sentence.
route_certifiedbooleanyesWhether the returned route has been checked against a tunnel inventory after it was computed. Always false: exclusion happens during the search, and only for roads the graph has a category for. A 200 is not a compliance certificate, and must not be relied on as one.

AppliedAvoidance: What the avoidance lists actually did, reported back so a 200 is never readable as a certificate.

FieldTypeAlways presentDescription
caveatsstring[]yesThe caveats that apply to this request, in words fit to repeat to a user. Always present when anything was applied.
costing_option_fieldsany JSONyesThe engine costing-option fields these words became, e.g. {"use_tolls": 0.0, "exclude_ferries": true}. This is the whole of what was sent, there is no hidden second mechanism.
avoidstring[]noThe avoid values that were applied, echoed back.
excludestring[]noThe exclude values that were applied, echoed back.

LandmarkSummary: The gateway's landmarks summary block.

FieldTypeAlways presentDescription
annotatedintegeryesHow many manoeuvres gained a landmark_instruction.
notestringnoWhy the count is what it is, when there is something to say: the per-route lookup cap was reached, or the deployment has no place index at all. Absent means the number is simply the number, so a zero with no note means this route genuinely passes nothing recognisable rather than that landmarks could not be looked up.

ManeuverOut: One turn-by-turn instruction of a computed route.

FieldTypeAlways presentDescription
distance_mnumberyesLength of the maneuver in metres.
duration_snumberyesEstimated duration of the maneuver in seconds.
instructionstringyesWritten instruction, e.g. "Turn right onto Main Street".
countrystringnoISO 3166-1 alpha-2 country of the road the manoeuvre starts on, e.g. "GB". This and the road fields below come from the MapMap gateway's road walk: present when the route was computed through the gateway (GATEWAY_URL + GATEWAY_API_KEY) and the map knows the road, absent otherwise. Never inferred.
driving_sidestringnoThe side of the road traffic keeps to there, "left" or "right"; present only when the road's country is known.
landmark_instructionstringnoThe same manoeuvre anchored to a recognisable place, e.g. "Turn right just after the Shell garage", present only when the request set landmarks: true and a place near the junction cleared the salience bar. Prefer reading this aloud when it is there: it is how a passenger would give the direction. instruction is always the engine's own and is never replaced.
refstringnoRoute numbers of that road ("; " separated, e.g. "A27" or "I 5; US 101"); absent when it carries none, never read from a street name.
road_classstringnoEngine road class of that road (motorway, trunk, primary, secondary, tertiary, unclassified, residential, service_other).
speed_limit_postedany JSONnoThe posted limit as the sign shows it, in the road's own unit: {"type":"known","value":30,"unit":"mph"} (plus "national":true for a national limit), {"type":"unlimited"} or {"type":"unknown"}. Read from the road the manoeuvre mostly travels along.
speed_limit_signstringnoThe speed-limit sign family there: "vienna" (roundel) or "mutcd" (US and Canadian rectangle).
speed_limit_unitstringnoThe unit speed limits are posted in on that road, "mph" or "km/h", from its country (never from the request's units).
subdivisionstringnoISO 3166-2 subdivision of that road where the map carries one, e.g. "GB-ENG" or "US-CA".

RouteRationale: Why the route goes the way it does, the route tool's opt-in rationale block, computed for truck, auto, bicycle, pedestrian and motor_scooter costings. Method honesty: the routing engine exposes no exclusion set, so entries are derived by re-routing with each declared constraint or preference relaxed (basis: "route_divergence"); this proves a field was binding and where, but never names the physical restriction or feature (the specific signed bridge, hill or unlit street). No live traffic or incident data enters the comparison and no delay is ever estimated from one, the engine has no live speed field.

FieldTypeAlways presentDescription
avoidedAvoidedConstraint[]yesThe declared constraints or preferences that changed the route, with locations and costs. Empty when nothing was binding.
caveatsstringyesThe method's limits, spelled out for relaying to users.
methodstringyesAlways "route_divergence".
notestringnoWhy the list is empty or incomplete, when it is.

AvoidedConstraint: One constraint or preference the route provably changed for, derived by route divergence (see RouteRationale).

FieldTypeAlways presentDescription
baselinestringyesWhat the probe compared against: "relaxed_to_non_binding" (truck constraints set to explicitly non-binding values), "engine_default" (preference removed so the engine default applies) or "cap_lifted" (a hard cap raised to its maximum).
basisstringyesAlways "route_divergence": the entry was derived by re-routing without the field and diffing geometry, not from sign or map-feature data.
constraintstringyesThe costing-options field that was relaxed ("height", "use_hills", …, or "combined").
distance_saved_mnumberyesDistance the constraint costs, metres.
diverged_length_mnumberyesLength of the diverging span along the constrained route, metres.
kindstringyesReason kind. Truck: "max_height", "max_width", "max_length", "max_weight", "hazmat", "adr_tunnel". Preferences: "hill_avoided", "surface_avoided", "road_avoided", "highway_avoided", "toll_avoided", "ferry_avoided", "living_street_avoided", "primary_road_avoided", "unlit_street_avoided", "hiking_difficulty_limited", "access_profile". Any costing: "combined". There is deliberately no "incident_avoided" or "traffic_delay_avoided": the engine consumes no live incident or traffic-speed input, so no divergence can be attributed to them.
locationLatLonyesWhere the constrained and unconstrained routes part ways.
reasonstringyesHuman-readable explanation, honest about the method.
rejoins_atLatLonyesWhere they rejoin.
time_saved_snumberyesTravel time the constraint costs (unconstrained is this much faster), seconds. Zero when the alternative is no faster.
valueany JSONyesThe declared value for the field (null for "combined"). NOTE: for truck this is the vehicle's dimension, not the infrastructure limit, the physical restriction is not identified.
fieldsstring[]noFor kind "combined": the constraint fields that only bind together.
unitstringnoUnit of value ("m", "t") when dimensional.

ScenicOffer: The scenic assessment beside a route: a peer offer, never a substitution. The fields here are the ones a client must actually read before saying anything. block keeps the gateway's whole answer beside them so a figure can always be audited back to the source that produced it, rather than to this mapping.

FieldTypeAlways presentDescription
availablebooleanyesWhether scenery could be assessed at all on this deployment. When false, note says why and nothing below was measured: that is "we could not look", never "there is nothing there".
blockany JSONyesThe gateway's whole scenic block, verbatim: candidates, feature shares, tile coverage, metering and budget.
caveatsstringyesThe standing limits of the method, for relaying to users.
chosenstringyesWhich route the assessment would offer: "fastest" (the quick way is already the pretty one, or nothing beat it) or "scenic".
alternative_geometry_polyline6stringnoThe scenic route's own geometry, polyline6, when there is one to offer. geometry_polyline6 on the route itself is byte-for-byte what the same request returns without the flag.
extra_time_snumbernoHow much longer the offered route takes than the fastest one, seconds, and the sentence for it. Absent when nothing was offered.
notestringnoWhy no assessment ran (only when available is false).
reasonstringnoThe plain, speakable sentence behind the offer. Null is an answer: nothing on this corridor measured above the floor, so no claim is made rather than one being invented.
rejectionsScenicRejection[]noWhy each candidate that lost lost. Empty when none did. Relay it: it is what makes "no prettier way" an attributable answer instead of a shrug.
spokenstringnoThe trade phrased for speech, e.g. "About seven minutes longer than the quick way." Prefer reading this aloud over the seconds.

ScenicRejection: Why one candidate route was not offered.

FieldTypeAlways presentDescription
codestringyesThe rejection code: same_route, no_scenic_gain, not_scenic_enough, over_time_budget or nothing_to_say.
detailstringyesThe code in words, including the minutes an over_time_budget candidate would have cost, so raising the budget is an informed choice rather than a guess.
sourcestringyesWhere the candidate came from: "fastest", "engine_alternate" or "no_motorway".

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  },
  "costing": "auto"
}

Result (trimmed):

json
{
  "distance_m": 5106,
  "duration_s": 917.918,
  "geometry_polyline6": "gejfaB~zrFDCRGXC^?rCXvD`@|@H`@J`@\\\\b@P^Pf@Nd@Px@Jp@HfA@fAC`Aq@fKeA|OK`ASlAY|@_@r…",
  "maneuvers": [
    {
      "country": "GB",
      "distance_m": 39,
      "driving_side": "left",
      "duration_s": 7.271,
      "instruction": "Drive south.",
      "road_class": "service_other",
      "speed_limit_posted": {
        "type": "unknown"
      },
      "speed_limit_sign": "vienna",
      "speed_limit_unit": "mph",
      "subdivision": "GB-ENG"
    },
    {
      "country": "GB",
      "distance_m": 23,
      "driving_side": "left",
      "duration_s": 5.026,
      "instruction": "Turn right.",
      "road_class": "service_other",
      "speed_limit_posted": {
        "type": "unknown"
      },
      "speed_limit_sign": "vienna",
      "speed_limit_unit": "mph",
      "subdivision": "GB-ENG"
    },
    "… 16 more"
  ],
  "summary": "5.1 km, 15 min (18 maneuvers), Found route between points",
  "view_url": "https://mapmap.ai/view?p=eyJ2IjoxLCJraW5kIjoicm91dGUiLCJjb3N0aW5nIjoiYXV0byIsInJ…"
}

More from the server's description:

Pass truck {height_m, width_m, length_m, gross_weight_t, hazmat, tunnel_code} to apply dimensional limits and the ADR dangerous-goods tunnel matrix to the search; pedestrian {use_lit 0-1, type "wheelchair"|"blind", max_hiking_difficulty 1-6, use_hills 0-1, max_grade_pct 1-90, surface_quality "avoid"|"ignore"} for lit-street walking, wheelchair routing and trail limits. Returns distance (m), duration (s), maneuvers, polyline6 geometry and the ADR costing that was applied. Prefer reading it aloud: it is how a passenger gives directions. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), whose place index does the lookup. Optional exclude_polygons for what-if scenarios ("close this bridge and re-route"): an array of polygons, each an array of [lon, lat] pairs forming one ring (longitude FIRST) whose intersecting roads are excluded from the search. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored. Each location also takes a kerbside approach: preferred_side "same" (alias "curb") stops on the door's side of the road, resolved against the locale's driving side, the left kerb in the UK, the right in Germany, with "opposite" and "either" (alias "unrestricted") for the rest. Pass emissions {vehicle_category, fuel, euro_standard} for UK clean-air-zone assessment: the response's zones block then names every zone the route enters and what THIS vehicle pays there, with the publishing authority cited. Needs the gateway, which holds the curated zone dataset; the whole dataset, every scheme, charge, boundary and provenance record, is readable at GET /v1/zones on the HTTP API when an agent needs to audit a figure or list zones without routing. EVERY OFFER STATES ITS REASON OR THERE IS NO OFFER: a route that scores well but cannot support a plain sentence is dropped rather than dressed up, so a scenic.reason of null means nothing here measured above the floor. A REJECTION IS ALSO AN ANSWER: every candidate that lost says why in scenic.rejections[] (same_route, no_scenic_gain, not_scenic_enough, over_time_budget with the minutes it would have cost, nothing_to_say), so "no prettier way" is always attributable and raising the budget is an informed choice. Needs the gateway, whose basemap archive the scoring is measured against. Returns view_url, a mapmap.ai link that draws the answer on a map (no key needed); the link carries the stop coordinates and names.

route_observations

What a journey PASSES, in sentences ready to be read aloud, each with its position along the route.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • NONE OF THIS IS IN A ROUTE: a route is a list of movements and a river crossing is not a movement, so do not try to read it out of route's answer.
  • Each observation carries at_m (WHERE it belongs (delivered anywhere else it is trivia, not an observation), a kind, a say sentence to relay verbatim, and a basis naming the map layer and tag the claim rests on.
  • THE SILENCE BUDGET IS THE DESIGN: min_gap_m is a floor, not a target, and when a route yields more than max_observations the gap WIDENS on its own so the survivors stay spread over the whole journey rather than clustering where the map happened to be richest.
  • So a short list on a long route is the budget working, not thin data.
  • READ coverage BEFORE REPORTING AN EMPTY LIST: an empty observations with tiles_read of 0 means NO DATA WAS READ, which is not the same claim as quiet countryside, and uncovered_m says how much of the route could not be seen at all.
  • Two things it will never say, deliberately: it never names a hill (a peak two kilometres away behind a ridge is named confidently and seen by nobody), and it never reads a junction number (an observation mistakable for an instruction is not safe to speak beside real guidance).
  • A settlement is a labelled POINT, not a boundary, and offset_m publishes the distance behind the word "through".
  • Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), whose basemap archive and elevation model answer it; a deployment with no archive answers 501 and says so.

Inputs

InputTypeRequiredDefaultLimitsDescription
geometry_polyline6stringyesThe route shape, precision-6 encoded: geometry_polyline6 straight out of the route tool's answer. Nothing else is accepted: there is no origin/destination form, because this tool annotates a route somebody already chose rather than computing one.
max_observationsintegerno40Ceiling on how many observations come back, 1 to 200 (default 40).
min_gap_mnumberno2000Least distance along the route between two observations, metres (default 2000). A FLOOR, not a target: when the route holds more than max_observations, the gap widens past this on its own so the survivors stay spread over the whole journey.
unitsstringnoUnits for the spoken distances: miles (default) or kilometers. kilometres and km are accepted too.

Output

FieldTypeAlways presentDescription
attributionstringyesThe licence notice for this answer. The obligation attaches to the product, not to each spoken sentence, so a visible or linked credit in the app satisfies it.
caveatstringyesThe standing limits of the method, for relaying to users.
coverageany JSONyesWhat the archive and the elevation model could and could not say: tiles_read, tiles_missing, uncovered_m, and the per-kind censuses. Read it before reporting an empty list. tiles_read of 0 means nothing was looked at, which is not the same claim as quiet countryside.
observationsRouteObservation[]yesThe observations, in route order, already thinned to the silence budget.
route_length_mnumberyesThe route's own length, metres, as walked.

RouteObservation: One thing worth saying about one point on a route.

FieldTypeAlways presentDescription
at_mnumberyesDistance along the route where this applies, metres. The whole point of the answer: said anywhere else it is trivia, not an observation.
basisstringyesWhich map layer and tag the claim rests on, so anybody who doubts one can go and look at the same feature.
kindstringyescrossing, landscape, settlement, watercourse, road or climb. Each claims a different thing and rests on different evidence.
saystringyesThe sentence to say. Prefer reading this aloud verbatim: it is already shaped for speech and already checked for length.
subjectstringyesThe subject's name exactly as the map records it, before any wording.
elevation_mnumbernoFor a climb, the height at the top, metres above sea level.
offset_mnumbernoFor a settlement, how far off the route the mapped centre lies, metres. A settlement is a labelled POINT, not a boundary, so this is the number behind the word "through" and nobody has to take it on trust.
run_mnumbernoFor a run-length observation (a road or a landscape), how far the run lasted, metres.

Example (called 2026-11-05)

json
{
  "geometry_polyline6": "gejfaB~zrFDCRGXC^?rCXvD`…"
}

Result (trimmed):

json
{
  "attribution": "Map data © OpenStreetMap contributors, ODbL. Basemap schema © OpenMapTiles, CC-BY 4.0.",
  "caveat": "Observations are computed by intersecting the route's geometry with OpenStreetMa…",
  "coverage": {
    "elevation": {
      "found": 21,
      "named": 21
    },
    "elevation_source": "model",
    "road": {
      "ambiguous_m": 0,
      "named_m": 100,
      "unnamed_m": 5000
    },
    "settlements": {
      "found": 16,
      "named": 8
    },
    "tiles_missing": 0,
    "tiles_read": 2,
    "tiles_unreadable": 0,
    "uncovered_m": 0,
    "watercourse_crossings": {
      "found": 0,
      "named": 0
    }
  },
  "observations": [
    {
      "at_m": 1700,
      "basis": "place.name",
      "kind": "settlement",
      "offset_m": 397.87514065536885,
      "say": "Passing through Covent Garden.",
      "subject": "Covent Garden"
    },
    {
      "at_m": 4900,
      "basis": "place.name",
      "kind": "settlement",
      "offset_m": 584.8831887682724,
      "say": "Passing through St Pancras.",
      "subject": "St Pancras"
    }
  ],
  "route_length_m": 5098.2
}

More from the server's description:

The named rivers and canals it crosses, the road it runs on and for how far, the settlements it goes through, the protected landscapes it enters and how high the road climbs: the things a passenger who knew the area would say. Give geometry_polyline6 straight from the route tool, plus optional units ("miles" default, or "kilometers"), min_gap_m and max_observations. Inside a window the rarer kind wins. It is a query and it triggers nothing: no position is held and nothing is pushed. COSTS 10 UNITS a call, flat, whatever is asked for.

heritage_narration

Three to five short spoken lines an hour about the ground a route is on: one sentence, at the point the driver reaches the thing it is about, and then silence.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Not a tour and not a chatbot.
  • Optional min_gap_s, min_gap_m, max_offset_m (default 60, hard ceiling 80), max_lines and voice_format ("opus" or "m4a", which adds each line's audio cache key and URL and NEVER synthesises).
  • COVERAGE IS ONE CORRIDOR, deliberately: the Chelsea riverside, 18 reviewed lines over 5 km.
  • ALWAYS READ THE CENSUS, which is the record of WHAT WAS NOT SAID: no lines with every entry in beyond_reach means this feature does not cover the journey; no lines with entries in silenced_by_budget means the corridor is covered and the budget is holding them back.
  • They are different answers and must never be relayed the same way.
  • On the reference route the census reports 1 spoken and 17 silenced, which is the design working, not a thin corpus.
  • Do not add either.

Inputs

InputTypeRequiredDefaultLimitsDescription
geometry_polyline6stringyesThe route shape, precision-6 encoded: geometry_polyline6 straight out of the route tool's answer.
duration_snumbernoThat route's own duration, seconds: duration_s from the same answer. Supply it. Without it the silence budget falls back to a distance, and "three to five lines an hour" is a claim about time that a distance answers wrongly at both ends.
max_linesintegerno12Ceiling on how many lines come back, 1 to 50 (default 12).
max_offset_mnumberno60, hard ceiling 80How near a plaque must come to the route before its line may be spoken, metres (default 60, hard ceiling 80).
min_gap_mnumberno400Least distance between two lines, metres (default 400). A floor under the time budget in every case.
min_gap_snumberno900, which is four an hourLeast time between two lines, seconds (default 900, which is four an hour). Applied only with duration_s.
voice_formatstringnoContainer to address each line's audio clip in: opus or m4a. Given, every line gains a clip with its synthesis cache key and URL. It NEVER synthesises: the key is a pure function of the sentence, so asking here buys nothing.

Output

FieldTypeAlways presentDescription
attributionstringyesThe licence notice for this answer.
caveatstringyesThe standing limits, for relaying to users.
censusany JSONyesThe census of what was NOT said: corpus_entries, within_reach, beyond_reach, spoken, silenced_by_budget, the gap that was applied and what it was derived from. An empty lines with every entry in beyond_reach means this feature does not cover the journey; an empty lines with entries in silenced_by_budget means the budget is working. They are different answers and must not be reported the same way.
corridorany JSONyesThe corridor that was consulted: its id, name, the road it runs on and the date its lines were reviewed.
linesHeritageLine[]yesThe lines, in route order. Empty is the ordinary answer nearly everywhere: read census before saying anything about it.
route_length_mnumberyesThe route's own length, metres.

HeritageLine: One reviewed line about the ground the route is on.

FieldTypeAlways presentDescription
at_mnumberyesDistance along the route where this belongs, metres.
basisstringyesWhat the line rests on, in words.
idstringyesThe corpus entry's own id, stable across responses, so a client can remember which lines it has already played.
offset_mnumberyesMeasured perpendicular distance from the plaque's own recorded coordinate to the route, metres. Published on every line because it is the entire warrant for the word "here", and because the coordinate behind it was recorded by a volunteer and has never been surveyed.
saystringyesThe sentence to say, verbatim. Written by a person from a public-domain plaque inscription and reviewed by a person.
subjectstringyesThe subject's name as the source dataset records it.
clipany JSONnoThe line's audio address (only when voice_format was given): hash, url and format. Nothing was synthesised to produce it.
plaque_idsinteger[]noThe source plaque records, by their OpenPlaques id, so any claim can be taken back to its source.

Example (called 2026-11-05)

json
{
  "geometry_polyline6": "wekeaBxksIsQzPtFdN~HxR`@…",
  "duration_s": 492.248
}

Result (trimmed):

json
{
  "attribution": "Plaque inscriptions from the OpenPlaques United Kingdom dataset (snapshot of 14 …",
  "caveat": "Every line is a sentence written and reviewed by a person from the inscription o…",
  "census": {
    "beyond_reach": 8,
    "corpus_entries": 18,
    "corridor": "chelsea-riverside",
    "gap_basis": "time",
    "gap_m": 5368.4,
    "mean_speed_kph": 21.5,
    "silenced_by_budget": 9,
    "spoken": 1,
    "within_reach": 10
  },
  "corridor": {
    "id": "chelsea-riverside",
    "name": "Chelsea riverside",
    "reviewed_on": "2026-09-21",
    "road": "A3220 and A3212, Lots Road to Millbank"
  },
  "lines": [
    {
      "at_m": 446.6,
      "basis": "inscription, and the dataset's own role for her",
      "id": "gaskell",
      "offset_m": 34.9,
      "plaque_ids": [
        380
      ],
      "say": "Elizabeth Gaskell was born along here. She wrote Cranford.",
      "subject": "Elizabeth Gaskell"
    }
  ],
  "route_length_m": 2936.2
}

More from the server's description:

NOTHING IS GENERATED HERE: every line is a sentence a person wrote from a public-domain plaque inscription and a person reviewed, compiled into the gateway binary, so no model runs in this path and the lines are identical for every driver. Give geometry_polyline6 from the route tool and, IMPORTANT, duration_s from the same answer: without it the silence budget falls back to a distance, and "four lines an hour" is a claim about time that a distance answers wrongly at both ends. EVERY OTHER ROUTE ANSWERS WITH NO LINES, and that is a correct answer rather than a failure. Every line publishes offset_m, the measured distance from the plaque to the route, because it is the entire warrant for the word "here", and plaque_ids so any claim can be taken back to its source. TWO OMISSIONS ARE DELIBERATE: no line says anything is visible, still standing or unchanged (a plaque records that somebody was somewhere, and nothing more), and NO LINE SAYS WHICH SIDE OF THE ROAD anything is on, because nothing in the source records it and a look that finds nothing spends the driver's trust in everything else. COSTS 1 UNIT a call: it reads no tiles and opens no archive. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).

matrix

Compute a travel time/distance matrix between origins (rows) and destinations (columns). Costing "auto", "truck" (with optional truck profile as in route), "bicycle", "pedestrian" or "motor_scooter".

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Inputs

InputTypeRequiredDefaultLimitsDescription
destinationsLatLon[]yesDestination locations (matrix columns).
originsLatLon[]yesOrigin locations (matrix rows).
costingCostingKindno"auto"Costing model: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
exclude_polygonsnumber[][][]noAreas to avoid, scenario analysis ("close this bridge and recompute the matrix"): an array of polygons, each an array of [lon, lat] pairs forming one exterior ring (GeoJSON-style, longitude FIRST). Roads intersecting any ring are excluded from every cell's path finding; a pair no path joins without them stays null (the search is never widened round them). Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck".

Output

FieldTypeAlways presentDescription
distances_mnumber[][]yesTravel distances in metres, same shape as durations_s. Null cells are unreachable pairs.
durations_snumber[][]yesTravel times in seconds; durations_s[i][j] is origin i → destination j. Null cells are unreachable pairs.

Example (called 2026-11-05)

json
{
  "origins": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    }
  ],
  "destinations": [
    {
      "lat": 51.5308,
      "lon": -0.1238
    },
    {
      "lat": 51.5194,
      "lon": -0.127
    }
  ],
  "costing": "auto"
}

Result (trimmed):

json
{
  "distances_m": [
    [
      5106,
      2846
    ]
  ],
  "durations_s": [
    [
      917.823,
      519.729
    ]
  ]
}

More from the server's description:

Returns durations_s[i][j] in seconds and distances_m[i][j] in metres; null cells are unreachable pairs. Up to 10,000 cells per call (origins × destinations). Optional exclude_polygons for before/after scenarios ("close this bridge and recompute the matrix"): an array of polygons, each an array of [lon, lat] pairs forming one ring (longitude FIRST) whose intersecting roads are excluded from every cell's path finding. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.

reachable_area

Compute the area reachable from an origin within one or more travel-time budgets, walkability/cyclability rings.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Costing "pedestrian" answers "how far can I walk in 15 minutes?", "bicycle" the cycling equivalent; "auto", "truck" and "motor_scooter" work too (e.g. delivery coverage).
  • An origin with no road within 5 km (open sea, desert, an unmapped area) is refused with "no roads near this point" rather than answered with a speck, and that refusal is not billed; move it onto land near a road.

Inputs

InputTypeRequiredDefaultLimitsDescription
contours_minutesnumber[]yesContour boundaries in minutes of travel time, e.g. [5, 10, 15] for 5/10/15-minute rings. 1 to 4 values (the routing engine's limit), each between 0 and 120 minutes.
originLatLonyesOrigin the reachable area is computed from.
costingCostingKindno"auto"Travel mode: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
exclude_polygonsnumber[][][]noAreas to avoid, scenario analysis ("close this bridge and recompute reachability"): an array of polygons, each an array of [lon, lat] pairs forming one exterior ring (GeoJSON-style, longitude FIRST). Roads intersecting any ring are excluded from the reachability search. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.
polygonsbooleannofalseReturn filled polygons instead of contour linestrings (default false). Polygons draw directly as a MapLibre fill layer.

Output

FieldTypeAlways presentDescription
geojsonany JSONyesGeoJSON FeatureCollection of the reachability contours, one feature per requested minute value (each feature's contour property is its minutes), as returned by the routing engine, drawn from the origin snapped onto the nearest usable road: the collection's snapped_origin {lat, lon, distance_m} (repeated on every feature's properties) says where that was.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "contours_minutes": [
    5
  ],
  "costing": "auto"
}

Result (trimmed):

json
{
  "geojson": {
    "engine": "valhalla",
    "eta_calibration": {
      "applied": false,
      "method": "gb_typical_delay_v1",
      "note": "Isochrone contours are drawn on the routing engine's free-flowing times, which d…",
      "travel_times": "free_flow"
    },
    "features": [
      {
        "geometry": "{…}",
        "properties": "{…}",
        "type": "Feature"
      }
    ],
    "snapped_origin": {
      "distance_m": 19.2,
      "lat": 51.500644,
      "lon": -0.124863
    },
    "type": "FeatureCollection"
  }
}

More from the server's description:

contours_minutes lists the ring boundaries in minutes (1 to 4 values, each up to 120); set polygons true for filled polygons ready to render as a map fill layer instead of contour lines. Returns a GeoJSON FeatureCollection, one feature per contour. The origin is first moved onto the nearest road the travel mode can use (ferries excluded, at most 5 km away) and the rings are drawn from there: snapped_origin {lat, lon, distance_m} on the collection and on every feature's properties says where. Optional exclude_polygons for before/after scenarios ("close this bridge and recompute reachability"): an array of polygons, each an array of [lon, lat] pairs forming one ring, longitude FIRST, whose intersecting roads are excluded from the reachability search. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.

elevation

Sample terrain elevation. Provide points (a bare list of coordinates) for point elevation, or encoded_polyline (optionally with resample_distance_m) for an along-route profile, not both.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Returns one sample per point/resampled point in order; elevation_m is null wherever the engine's DEM tile set has no coverage at that point (never a guess).

Inputs

InputTypeRequiredDefaultLimitsDescription
encoded_polylinestringnoA route as a Google encoded polyline with six digits of precision. Provide this or points, not both.
pointsLatLon[]noPoints to sample. Provide this or encoded_polyline, not both.
resample_distance_mnumbernoResamples encoded_polyline at this spacing in metres before sampling height (ignored for points).

Output

FieldTypeAlways presentDescription
samplesElevationSample[]yesOne sample per input point (or per resampled shape point, in the encoded_polyline form), in order.

ElevationSample: One elevation sample: elevation_m and, when the request set encoded_polyline, the resampled point and its cumulative distance from the start.

FieldTypeAlways presentDescription
elevation_mnumbernoElevation in metres, or null when the engine has no DEM tile coverage at this point, Valhalla's own honest value, never a guess.
latnumbernoLatitude of the (possibly resampled) point, when known.
lonnumbernoLongitude of the (possibly resampled) point, when known.
range_kmnumbernoCumulative distance from the first point in kilometres, present only for the encoded_polyline form.

Example (called 2026-11-05)

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.4769,
      "lon": -0.0005
    }
  ]
}

Result (trimmed):

json
{
  "samples": [
    {
      "elevation_m": 16.37
    },
    {
      "elevation_m": 48.28
    }
  ]
}

More from the server's description:

The encoded_polyline form also returns each sample's resampled lat/lon and cumulative range_km from the start.

match_trace

Snap a recorded GPS trace to the road network and say what it actually travelled over. Provide shape (2 to 2000 recorded points, oldest first) or encoded_polyline (the same trace as a polyline6 string) (not both) plus the costing it was travelled under: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Returns the matched path as geometry_polyline6 (the snapped roads, not your raw points) with its distance_m and duration_s, then the roll-ups: by_road_class and by_admin (distance and time, longest first), by_surface (distance), and toll, bridge and tunnel totals.
  • Honesty: the roll-ups are summed per matched road segment, so they need not add up to distance_m exactly, and segments the map records no surface or admin area for are left out of that breakdown rather than filed under a guess, an entry in by_admin with null codes is exactly that, counted and not attributed.

Inputs

InputTypeRequiredDefaultLimitsDescription
costingCostingKindnoCosting model the trace was travelled under: auto (default), truck, bicycle, pedestrian or motor_scooter. It decides which roads the trace may be matched onto, so a walked trace matched as auto snaps to the carriageway rather than the path.
encoded_polylinestringnoThe trace as a Google encoded polyline with six digits of decimal precision (polyline6), the geometry route and match_trace themselves return. Provide this or shape.
shapeLatLon[]noThe trace as an ordered list of recorded points, oldest first. Between 2 and 2000 points. Provide this or encoded_polyline.

Output

FieldTypeAlways presentDescription
bridgeSegmentSummaryyesBridge portion of the matched path.
by_adminAdminAreaSummary[]yesDistance and time by administrative area, longest first. An entry whose codes are all null covers segments the graph could not place in any admin area, counted honestly rather than guessed at.
by_road_classRoadClassSummary[]yesDistance and time by road class (motorway, primary, residential, …), longest first.
by_surfaceSurfaceSummary[]yesDistance by road surface (paved, paved_smooth, gravel, …), longest first. Empty when the graph records no surface for any matched segment.
costingstringyesCosting the trace was matched under.
distance_mnumberyesLength of the matched path in metres.
duration_snumberyesTravel time along the matched path in seconds, from the engine's own time model.
edge_countintegeryesHow many road segments the trace matched onto.
geometry_polyline6stringyesThe matched path as a Google encoded polyline with six digits of decimal precision (polyline6). This is the trace snapped to real roads, not the raw input.
summarystringyesOne-line human summary of the match, for reading aloud.
tollSegmentSummaryyesTolled portion of the matched path.
tunnelSegmentSummaryyesTunnel portion of the matched path.

SegmentSummary: How much of the matched path carried one flag (toll, bridge, tunnel). The engine reports no separate time for these, so distance and a count are all that can honestly be given.

FieldTypeAlways presentDescription
distance_mnumberyesDistance carrying this flag in metres. Zero means none of the matched path did.
edge_countintegeryesHow many matched segments carried this flag.

AdminAreaSummary: Distance and time the matched path spent in one administrative area.

FieldTypeAlways presentDescription
distance_mnumberyesDistance in this area in metres.
duration_snumberyesTime in this area in seconds.
edge_countintegeryesHow many matched segments fell in this area.
countrystringnoCountry name, or null as for country_code.
country_codestringnoISO 3166-1 alpha-2 country code, or null where the routing graph places the segment in no admin area at all.
statestringnoState/region name, or null when the graph records none.
state_codestringnoState/region code, or null when the graph records none.

RoadClassSummary: Distance and time the matched path spent on one road class.

FieldTypeAlways presentDescription
distance_mnumberyesDistance on this road class in metres.
duration_snumberyesTime on this road class in seconds.
edge_countintegeryesHow many matched segments carried this road class.
road_classstringyesThe road class, as the routing graph records it: motorway, trunk, primary, secondary, tertiary, unclassified, residential, service_other.

SurfaceSummary: Distance the matched path spent on one road surface.

FieldTypeAlways presentDescription
distance_mnumberyesDistance on this surface in metres.
edge_countintegeryesHow many matched segments carried this surface.
surfacestringyesThe surface, as the routing graph records it: paved_smooth, paved, paved_rough, compacted, dirt, gravel, path, impassable.

Example (called 2026-11-05)

json
{
  "shape": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5033,
      "lon": -0.1276
    },
    {
      "lat": 51.5068,
      "lon": -0.128
    }
  ],
  "costing": "auto"
}

Result (trimmed):

json
{
  "bridge": {
    "distance_m": 0,
    "edge_count": 0
  },
  "by_admin": [
    {
      "country": "United Kingdom",
      "country_code": "GB",
      "distance_m": 1123,
      "duration_s": 314.7,
      "edge_count": 60,
      "state": "England",
      "state_code": "ENG"
    }
  ],
  "by_road_class": [
    {
      "distance_m": 657,
      "duration_s": 81.7,
      "edge_count": 29,
      "road_class": "primary"
    },
    {
      "distance_m": 229,
      "duration_s": 139.1,
      "edge_count": 11,
      "road_class": "service_other"
    },
    "… 2 more"
  ],
  "by_surface": [
    {
      "distance_m": 1086,
      "edge_count": 57,
      "surface": "paved_smooth"
    },
    {
      "distance_m": 37,
      "edge_count": 3,
      "surface": "paved"
    }
  ],
  "costing": "auto",
  "distance_m": 1065,
  "duration_s": 293.945,
  "edge_count": 60,
  "geometry_polyline6": "myjfaBphrFGdEI`\\EvEInN{DIkFK{Wc@cNUqHa@e@r[E~BC`CSpJiDA{CQmBGgD?ySLwOHuADaLPAbB?…",
  "summary": "matched 1.1 km, 4 min over 60 road segment(s) as auto",
  "toll": {
    "distance_m": 0,
    "edge_count": 0
  },
  "tunnel": {
    "distance_m": 0,
    "edge_count": 0
  }
}

More from the server's description:

Costing decides which roads the trace may match onto, so a walk matched as "auto" snaps to the carriageway rather than the footpath. This is how you turn a dashcam or telematics log into a report, which country and region the driving happened in, how much of it was motorway, how much was tolled, how much was unpaved. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY); there is no direct-backend fallback.

Fleet and optimisation

Multi-stop and multi-vehicle planning, re-planning mid-shift, EV journeys and errands against a deadline.

optimise_routes

Optimise multi-vehicle, multi-stop delivery plans (VRP). Provide vehicles (id, start/end, capacity, skills, time_window), jobs (id, location, service_s, delivery/pickup, skills, time_windows) and/or shipments (pickup+delivery pairs that ride the same vehicle).

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Optional territories are named polygons ([{id, polygon}], GeoJSON [lon, lat] rings, LONGITUDE FIRST) that bound who serves what: a vehicle listing territory_ids may serve a task only if that task sits inside at least one of the territories it names, while a vehicle listing none is unrestricted and may serve anything, inside a round or outside every one.
  • That split is fixed BEFORE the solve, so the plan is conservative and never optimistic, it cannot put a lorry in two places at once, but it is an approximation: a trip that finishes early cannot lend its spare time to the next, so stops can come back unassigned that a truly sequential model would have served, and max_trips is a budget rather than a prediction (ask for five on a shift that supports three and every window shrinks to a fifth).
  • relax_if_unassigned {time_windows_by_s?, allow_overtime_s?} re-solves ONCE with those relaxations if the first plan left work unassigned, and the relaxation block says honestly which plan came back: at most one second solve, never beyond the caps stated, and the relaxed plan is returned ONLY if it assigns more work than the first.
  • Breaks are never widened, a driver's rest is not a preference to trade for a fuller van , and neither are capacities, skills, territories or task caps; only time windows move.
  • Territories, reloads, relaxation and zones are computed by the MapMap gateway; without one configured the tool refuses rather than returning a plan that quietly ignored them.

Inputs

InputTypeRequiredDefaultLimitsDescription
vehiclesOptimiseVehicleSpec[]yesThe fleet (at least one vehicle, each with a start and/or end).
avoid_zonesbooleannoKeep the optimisation's travel-time matrix out of every zone the declared vehicle would be charged or banned in. Requires emissions.
costingCostingKindno"auto"Costing model for the travel-time matrix: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
emissionsEmissionsSpecnoThe fleet's emission declaration, for UK clean-air / low-emission zone assessment. On its own it annotates: the response's zones block names every zone containing one of the problem's own locations and what this vehicle would pay there. With avoid_zones it also steers the internal travel-time matrix away from those zones, so the plan itself changes.
jobsOptimiseJobSpec[]noSingle-stop jobs (at least one job or shipment overall).
relax_if_unassignedRelaxSpecnoRe-solve ONCE with these relaxations if the first solve leaves work unassigned, and say honestly which plan came back. At most one second solve, never beyond the caps you state, and the relaxed plan is returned only if it assigns MORE work than the first, giving away constraints for nothing is strictly worse than not giving them away. Breaks are never widened, nor are capacities, skills, territories or task caps: only time windows move, and only by the stated amounts. Bills as two solves when the second one runs.
shipmentsOptimiseShipmentSpec[]noPickup+delivery pairs.
territoriesTerritorySpec[]noFleet territories: named polygons that bound which vehicle may serve which stop, referenced by vehicles[].territory_ids. These are request data, caller-drawn rounds, validated per call and never stored. Nothing to do with clean-air zones or with the offline map packages of the same word.
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; the travel-time matrix then respects dimensional and dangerous-goods restrictions, so the whole plan is truck-legal.

Output

FieldTypeAlways presentDescription
profilestringyesThe matrix costing profile the plan was computed with ("auto", "truck", "bicycle", "pedestrian" or "motor_scooter").
routesOptimisedRoute[]yesOne optimised route per used vehicle.
summaryOptimiseSummaryyesSolution summary.
unassignedUnassignedTask[]yesTasks the solver could not assign to any vehicle.
relaxationany JSONnoThe relaxation report: the caps requested, whether a second solve ran, whether ITS plan is the one returned, what was widened, and what is still unassigned. Present only when relax_if_unassigned was declared. Always read second_solve and relaxed_plan_used before telling anyone the day fits. A plan produced under relaxation has had promises moved, and the block is what says so.
reloadsany JSONnoThe multi-trip split: each vehicle's trips, their windows, the depot each returns to, and the stated approximation. Present only when a vehicle declared reloads. Read the basis inside it before quoting arrival times. The trip windows are fixed BEFORE the solve, so a lorry that tips early cannot lend the spare time to its next trip: stops can come back unassigned that a truly sequential model would have served. The plan is feasible, never optimistic, it cannot put a vehicle in two places at once, but it is not optimal. Re-plan after each tip through replan_routes for the tighter answer.
territoriesany JSONnoHow the territories bound the plan: which vehicle was eligible for what, and any task no eligible vehicle existed for. Present only when territories was declared.
view_urlstringnoA mapmap.ai link that draws this fleet plan, one route per vehicle, on a map: open it or pass it on as the last line of the answer. Nothing is stored server-side and opening it needs no key; the link itself carries the stop coordinates and stop names (plus any arrival clocks, dwell times and totals) and nothing else, so anyone holding it can read those stops. Absent when the plan cannot be linked (see view_url_note) or the server runs with links turned off (SN_MCP_SITE_URL empty).
view_url_notestringnoWhy there is no view_url (too many vehicles or stops for one link, or over the 6,000 character cap), or what the link left out to fit under the cap (arrival times, dwell times, then stop names). Relay it with the link.
zonesany JSONnoClean-air / low-emission zones touching the problem's own locations, and what the declared vehicle pays in each. Present only when emissions was declared.

Example (called 2026-11-05)

json
{
  "vehicles": [
    {
      "id": 1,
      "start": {
        "lat": 51.5007,
        "lon": -0.1246
      },
      "end": {
        "lat": 51.5007,
        "lon": -0.1246
      }
    }
  ],
  "jobs": [
    {
      "id": 1,
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      }
    },
    {
      "id": 2,
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      }
    }
  ],
  "costing": "auto"
}

Result (trimmed):

json
{
  "profile": "auto",
  "routes": [
    {
      "distance_m": 9166,
      "duration_s": 1638,
      "service_s": 0,
      "steps": [
        "{…}",
        "{…}",
        "… 2 more"
      ],
      "vehicle": 1,
      "waiting_time_s": 0
    }
  ],
  "summary": {
    "cost": 1638,
    "distance_m": 9166,
    "duration_s": 1638,
    "routes": 1,
    "service_s": 0,
    "unassigned": 0,
    "waiting_time_s": 0
  },
  "unassigned": [],
  "view_url": "https://mapmap.ai/view?p=eyJ2IjoxLCJraW5kIjoiZmxlZXQiLCJjb3N0aW5nIjoiYXV0byIsInJ…",
  "zones": {
    "at_locations": [
      {
        "assessment": "unknown",
        "currency": "GBP",
        "daily_charge": null,
        "enforcement": "charge",
        "id": "gb-london-lez",
        "locations_inside": 3,
        "name": "London Low Emission Zone",
        "operator": "Transport for London",
        "source": "{…}",
        "type": "lez"
      },
      {
        "assessment": "unknown",
        "currency": "GBP",
        "daily_charge": null,
        "enforcement": "charge",
        "id": "gb-london-ulez",
        "locations_inside": 3,
        "name": "London Ultra Low Emission Zone",
        "operator": "Transport for London",
        "source": "{…}",
        "type": "ulez"
      }
    ],
    "basis": "the optimiser is fed a travel-time matrix, not road geometry, so this block repo…",
    "caveats": "Zone boundaries are simplified from the published geometry to the tolerance stat…",
    "note": "charge depends on vehicle emissions, supply `emissions` (vehicle_category, fuel,…"
  }
}

More from the server's description:

Costing "auto", "truck", "bicycle", "pedestrian" or "motor_scooter" (cargo-bike and courier fleets welcome): with a truck profile (dimensions + ADR declaration, as in route), the travel-time matrix respects dimensional and dangerous-goods restrictions, so every optimised route is truck-legal. Returns a summary, unassigned tasks and per-vehicle routes with ordered steps (arrival_s/duration_s in seconds, distance_m in metres). Fair use: at most 200 unique locations per problem, and no wider than the routing engine's matrix span (1,500 km on the hosted endpoint for motor costings, 400 km stock on a self-host). Past that, cluster the stops with cluster and optimise each group, or submit the whole problem to the asynchronous lane with submit_optimise_job (2,000 locations). The response's territories block says which vehicle was eligible for what and names any task no vehicle could take. A vehicle may also declare reloads {max_trips 2-5, reload_time_s, depot?} to return to a depot, reload and go out again, the tipping round. It needs a time_window, because the shift is what gets split: it is cut into that many consecutive non-overlapping windows separated by the reload time, each trip carrying the vehicle's FULL capacity and task caps. Read the returned reloads block before quoting arrival times, and re-plan after each tip with replan_routes for the tighter answer. It bills as two solves when the second one runs. Always check relaxation.relaxed_plan_used before telling anyone the day fits: a plan produced under relaxation has had promises moved. emissions {vehicle_category, fuel, euro_standard} annotates the plan with the clean-air zones its own stops sit in and what this vehicle pays in each; add avoid_zones: true to steer the travel-time matrix out of them, which changes the plan itself. Returns view_url, a mapmap.ai link that draws the answer on a map (no key needed); the link carries the stop coordinates and names.

order_stops

Put a single run's stops in the best visiting order ("order my errands"). Provide start {lat, lon} and stops (1-100 entries of {location, label?, service_s?}); optionally an end destination or round_trip: true to return to the start.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Inputs

InputTypeRequiredDefaultLimitsDescription
startLatLonyesWhere the run starts.
stopsOrderStopSpec[]yesThe stops to put in the best visiting order (1–100).
costingCostingKindno"auto"Costing model: "auto" (default) or "truck".
endLatLonnoOptional fixed final destination. Mutually exclusive with round_trip; omitted (and not a round trip), the run ends at whichever stop the solver visits last.
round_tripbooleannofalseReturn to start after the last stop (default false).
truckTruckSpecnoTruck profile (dimensions + ADR declaration); requires costing "truck". The travel-time matrix then respects dimensional and dangerous-goods restrictions.

OrderStopSpec: One errand stop of an order_stops request.

InputTypeRequiredDefaultLimitsDescription
locationLatLonyesThe stop's location.
labelstringnoHuman-readable label echoed back in the ordered plan (e.g. "chemist" or "site B").
service_sintegernoOn-site time in seconds (waiting, loading, shopping).

Output

FieldTypeAlways presentDescription
duration_sintegeryesTotal travel time in seconds.
orderedOrderedStop[]yesThe stops in optimal visiting order.
profilestringyesThe matrix costing profile ("auto" or "truck").
unassigned_stop_indexesinteger[]yes0-based indexes of stops the solver could not fit (empty in the normal, unconstrained case).
distance_mintegernoTotal travel distance in metres, when reported.

OrderedStop: One stop of an ordered errand plan.

FieldTypeAlways presentDescription
arrival_sintegeryesArrival time as an offset from departure, in seconds.
locationLatLonyesThe stop's location.
orderintegeryes1-based visit order.
stop_indexintegeryesThe stop's 0-based index in the request's stops array.
labelstringnoThe request's label for this stop, when one was given.

Example (called 2026-11-05)

json
{
  "start": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "stops": [
    {
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      },
      "label": "B"
    },
    {
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      },
      "label": "C"
    }
  ]
}

Result (trimmed):

json
{
  "distance_m": 4791,
  "duration_s": 868,
  "ordered": [
    {
      "arrival_s": 520,
      "label": "C",
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      },
      "order": 1,
      "stop_index": 1
    },
    {
      "arrival_s": 868,
      "label": "B",
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      },
      "order": 2,
      "stop_index": 0
    }
  ],
  "profile": "auto",
  "unassigned_stop_indexes": []
}

More from the server's description:

Costing "auto" = car, "truck" = lorry (pass truck as in route for a truck-legal order). Returns the stops in visit order with arrival offsets in seconds, plus total duration and distance.

cluster

Group stops into balanced geographic clusters, so a day too large for one optimisation can be optimised one cluster at a time.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • IMPORTANT, this is STRAIGHT-LINE clustering.
  • Distances are measured between coordinates, not along the road network: no road, river, motorway junction or one-way system is consulted, and two stops either side of an estuary look adjacent.
  • The answer carries a basis sentence saying exactly this; show it, so a centroid is never read as a plan.

Inputs

InputTypeRequiredDefaultLimitsDescription
locationsClusterLocationSpec[]yesThe stops to group. Ids must be unique; at most 5,000.
clustersintegernoHow many clusters to produce, balanced by stop count. Give exactly one of clusters, max_cluster_locations or max_cluster_load.
max_cluster_loadnumbernoAt most this much summed load per cluster; the cluster count is derived from it. A load ceiling with lumpy loads is a bin-packing problem and may have no solution at the derived count, the response says so rather than pretending.
max_cluster_locationsintegernoAt most this many stops per cluster; the cluster count is derived from it.
seedintegerno42Seed for the k-means++ seeding (default 42). The same request with the same seed always returns the same clusters, on every deployment.
territoriesTerritorySpec[]noOptional territories. Given, no cluster straddles one: each territory is clustered on its own, and so are the stops inside none of them.

ClusterLocationSpec: One stop to be clustered.

InputTypeRequiredDefaultLimitsDescription
idstringyesCaller-chosen id, echoed back as the cluster's membership. Must be unique within the request.
latnumberyesLatitude in decimal degrees.
lonnumberyesLongitude in decimal degrees.
loadnumbernoOptional weight, parcels, kilograms, litres, minutes of service. Summed per cluster and reported; constrains the clustering only under max_cluster_load.

Output

FieldTypeAlways presentDescription
balanceany JSONyesThe constraint applied, the largest cluster produced, and whether the ceiling had to be relaxed to place every stop.
basisstringyesThe method statement: this is STRAIGHT-LINE clustering. Distances are between coordinates, not along roads, two stops either side of an estuary look adjacent. Show it. It is the sentence that stops a centroid being read as a plan.
clustersany JSONyesThe clusters: each with its id, member locations (your ids), count, summed load, centroid and the territory it belongs to.
parametersany JSONyesThe seed, the cluster count, the locations seen, how many territories were used, the iterations run and whether it converged.

Example (called 2026-11-05)

json
{
  "locations": [
    {
      "id": "a",
      "lat": 51.5,
      "lon": -0.12
    },
    {
      "id": "b",
      "lat": 51.51,
      "lon": -0.13
    },
    {
      "id": "c",
      "lat": 51.6,
      "lon": -0.2
    },
    {
      "id": "d",
      "lat": 51.61,
      "lon": -0.21
    }
  ],
  "clusters": 2
}

Result (trimmed):

json
{
  "balance": {
    "basis": "locations",
    "largest_cluster_load": 0,
    "largest_cluster_locations": 2,
    "max_load_per_cluster": null,
    "max_locations_per_cluster": null,
    "note": "every cluster is inside the ceiling.",
    "relaxed": false
  },
  "basis": "Straight-line clustering. Distances are measured between coordinates on a local …",
  "clusters": [
    {
      "centroid": {
        "lat": 51.504999999999995,
        "lon": -0.125
      },
      "count": 2,
      "id": 0,
      "load": 0,
      "locations": [
        "a",
        "b"
      ],
      "territory": null
    },
    {
      "centroid": {
        "lat": 51.605,
        "lon": -0.205
      },
      "count": 2,
      "id": 1,
      "load": 0,
      "locations": [
        "c",
        "d"
      ],
      "territory": null
    }
  ],
  "parameters": {
    "clusters": 2,
    "converged": true,
    "iterations": 2,
    "locations": 4,
    "locations_outside_all_territories": 0,
    "seed": 42,
    "territories": 0
  }
}

More from the server's description:

This is the front half of the recipe for a thousand-stop day: cluster here, then call optimise_routes per cluster, where the routing engine's own matrix decides the visiting order. That makes it the right tool for deciding which stops belong TOGETHER and the wrong one for deciding what ORDER to visit them in. Provide locations ([{id, lat, lon, load?}], ids unique, at most 5,000) and EXACTLY ONE of clusters (how many groups, balanced by stop count), max_cluster_locations or max_cluster_load (a per-cluster ceiling the count is derived from). Optional territories keep a cluster from straddling a round: each is clustered on its own, and so are the stops inside none of them. Optional seed (default 42) drives the seeding, the same request with the same seed always returns the same clusters, on every deployment, so a re-run is a re-run. Returns each cluster's member ids, count, summed load, centroid and territory, plus a balance block naming the constraint applied and whether it had to be relaxed to place every stop: a load ceiling with lumpy loads is a bin-packing problem and may have no solution at the derived count. Requires the MapMap gateway.

replan_routes

Re-plan a fleet part-way through its shift. Send the day back; nothing is stored.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • MapMap holds NO dispatch state, no plan, no vehicle position, no completion log, so a re-plan is not a delta against something we remember: you pass the ORIGINAL optimise_routes problem in full, plus progress (per vehicle: completed_stop_ids IN THE ORDER SERVED, an optional current_position, and unavailable: true for a breakdown or an end of hours) and/or changes (cancel_job_ids, add_jobs, add_shipments), and get a fresh plan for what is left.
  • That costs bandwidth and buys the absence of a server-side plan that can go stale, leak, or fall out of step with the telematics platform that actually owns the truth, and it makes a re-plan reproducible: the same body always yields the same answer.
  • Completed stops are LOCKED by construction: they are removed from the problem entirely and each vehicle starts from where it actually is, so the solver cannot move a stop that has already happened, a guarantee the solver cannot break, rather than a hint it is free to ignore.
  • Returns the same plan shape as optimise_routes for the REMAINING work, plus a replan block: the prefix locked per vehicle, where each re-plans from and how that was decided, stops released from a vehicle that can no longer serve them, tasks forced unassigned, and a note for every id that did not resolve.
  • Read that block, a stop that vanished from the plan is named there rather than left for a dispatcher to notice at four in the afternoon; a cancelled id that matched nothing is reported there too rather than refused.
  • Multi-trip vehicles cannot be re-planned: a completion does not say which trip it belongs to, so a problem whose vehicles declare reloads is refused with what to send instead.
  • Billed on the remaining problem, not the original.

Inputs

InputTypeRequiredDefaultLimitsDescription
vehiclesOptimiseVehicleSpec[]yesThe fleet (at least one vehicle, each with a start and/or end).
avoid_zonesbooleannoKeep the optimisation's travel-time matrix out of every zone the declared vehicle would be charged or banned in. Requires emissions.
changesReplanChangesSpecnoChanges to the work itself. Optional, on the same condition.
costingCostingKindno"auto"Costing model for the travel-time matrix: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
emissionsEmissionsSpecnoThe fleet's emission declaration, for UK clean-air / low-emission zone assessment. On its own it annotates: the response's zones block names every zone containing one of the problem's own locations and what this vehicle would pay there. With avoid_zones it also steers the internal travel-time matrix away from those zones, so the plan itself changes.
jobsOptimiseJobSpec[]noSingle-stop jobs (at least one job or shipment overall).
progressReplanProgressSpec[]noPer-vehicle progress. Optional, but at least one progress entry or one change is required, a re-plan that reports nothing new is the original problem.
relax_if_unassignedRelaxSpecnoRe-solve ONCE with these relaxations if the first solve leaves work unassigned, and say honestly which plan came back. At most one second solve, never beyond the caps you state, and the relaxed plan is returned only if it assigns MORE work than the first, giving away constraints for nothing is strictly worse than not giving them away. Breaks are never widened, nor are capacities, skills, territories or task caps: only time windows move, and only by the stated amounts. Bills as two solves when the second one runs.
shipmentsOptimiseShipmentSpec[]noPickup+delivery pairs.
territoriesTerritorySpec[]noFleet territories: named polygons that bound which vehicle may serve which stop, referenced by vehicles[].territory_ids. These are request data, caller-drawn rounds, validated per call and never stored. Nothing to do with clean-air zones or with the offline map packages of the same word.
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; the travel-time matrix then respects dimensional and dangerous-goods restrictions, so the whole plan is truck-legal.

ReplanChangesSpec: What has changed about the work since the plan was made.

InputTypeRequiredDefaultLimitsDescription
add_jobsOptimiseJobSpec[]noNew single-stop jobs, in exactly the optimise_routes job shape.
add_shipmentsOptimiseShipmentSpec[]noNew pickup+delivery pairs, in exactly the optimise_routes shipment shape.
cancel_job_idsinteger[]noIds of jobs that no longer need doing. A job already reported completed cannot be cancelled; the response says so rather than silently dropping it.

ReplanProgressSpec: One vehicle's progress through its shift.

InputTypeRequiredDefaultLimitsDescription
vehicleintegeryesThe vehicle this progress belongs to, an id from vehicles.
completed_stop_idsinteger[]noIds of the stops this vehicle has already served, IN THE ORDER IT SERVED THEM. Each is a job id or a shipment pickup/delivery id. This is the locked prefix: it already happened, so no re-plan may move it.
current_positionLatLonnoWhere the vehicle is now; becomes its start for the re-plan. Omitted, its last completed stop is used; with neither, its original start.
unavailablebooleannoThe vehicle has dropped out of the shift, breakdown, illness, end of hours. It leaves the fleet for the re-plan; what it already completed stays completed.

Output

FieldTypeAlways presentDescription
profilestringyesThe matrix costing profile the plan was computed with ("auto", "truck", "bicycle", "pedestrian" or "motor_scooter").
replanany JSONyesWhat the re-plan locked and why: the prefix held per vehicle, where each vehicle re-plans from and how that was decided, stops released from a vehicle that could no longer serve them, tasks forced unassigned, and a note for every id that did not resolve. Read it. A stop that vanished from the plan is named here rather than left for the dispatcher to notice at four in the afternoon.
routesOptimisedRoute[]yesOne optimised route per used vehicle.
summaryOptimiseSummaryyesSolution summary.
unassignedUnassignedTask[]yesTasks the solver could not assign to any vehicle.
relaxationany JSONnoThe relaxation report: the caps requested, whether a second solve ran, whether ITS plan is the one returned, what was widened, and what is still unassigned. Present only when relax_if_unassigned was declared. Always read second_solve and relaxed_plan_used before telling anyone the day fits. A plan produced under relaxation has had promises moved, and the block is what says so.
reloadsany JSONnoThe multi-trip split: each vehicle's trips, their windows, the depot each returns to, and the stated approximation. Present only when a vehicle declared reloads. Read the basis inside it before quoting arrival times. The trip windows are fixed BEFORE the solve, so a lorry that tips early cannot lend the spare time to its next trip: stops can come back unassigned that a truly sequential model would have served. The plan is feasible, never optimistic, it cannot put a vehicle in two places at once, but it is not optimal. Re-plan after each tip through replan_routes for the tighter answer.
territoriesany JSONnoHow the territories bound the plan: which vehicle was eligible for what, and any task no eligible vehicle existed for. Present only when territories was declared.
view_urlstringnoA mapmap.ai link that draws this fleet plan, one route per vehicle, on a map: open it or pass it on as the last line of the answer. Nothing is stored server-side and opening it needs no key; the link itself carries the stop coordinates and stop names (plus any arrival clocks, dwell times and totals) and nothing else, so anyone holding it can read those stops. Absent when the plan cannot be linked (see view_url_note) or the server runs with links turned off (SN_MCP_SITE_URL empty).
view_url_notestringnoWhy there is no view_url (too many vehicles or stops for one link, or over the 6,000 character cap), or what the link left out to fit under the cap (arrival times, dwell times, then stop names). Relay it with the link.
zonesany JSONnoClean-air / low-emission zones touching the problem's own locations, and what the declared vehicle pays in each. Present only when emissions was declared.

Example (called 2026-11-05)

json
{
  "vehicles": [
    {
      "id": 1,
      "start": {
        "lat": 51.5007,
        "lon": -0.1246
      },
      "end": {
        "lat": 51.5007,
        "lon": -0.1246
      }
    }
  ],
  "jobs": [
    {
      "id": 1,
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      }
    },
    {
      "id": 2,
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      }
    }
  ],
  "costing": "auto",
  "progress": [
    {
      "vehicle": 1,
      "completed_stop_ids": [
        1
      ]
    }
  ]
}

Result (trimmed):

json
{
  "profile": "auto",
  "replan": {
    "basis": "MapMap stores no dispatch state. Completed stops are locked by removing them fro…",
    "changes_applied": {
      "added_jobs": [],
      "added_shipments": [],
      "cancelled_jobs": [],
      "completed_stops": 1,
      "vehicles_unavailable": []
    },
    "locked_stops_per_vehicle": [
      {
        "start_basis": "last_completed_stop",
        "start_from": "{…}",
        "stops": [
          1
        ],
        "unavailable": false,
        "vehicle": 1
      }
    ],
    "mechanism": "locked-prefix elision",
    "notes": [],
    "released_stops": []
  },
  "routes": [
    {
      "distance_m": 4288,
      "duration_s": 770,
      "service_s": 0,
      "steps": [
        "{…}",
        "{…}",
        "… 1 more"
      ],
      "vehicle": 1,
      "waiting_time_s": 0
    }
  ],
  "summary": {
    "cost": 770,
    "distance_m": 4288,
    "duration_s": 770,
    "routes": 1,
    "service_s": 0,
    "unassigned": 0,
    "waiting_time_s": 0
  },
  "unassigned": [],
  "zones": {
    "at_locations": [
      {
        "assessment": "unknown",
        "currency": "GBP",
        "daily_charge": null,
        "enforcement": "charge",
        "id": "gb-london-lez",
        "locations_inside": 3,
        "name": "London Low Emission Zone",
        "operator": "Transport for London",
        "source": "{…}",
        "type": "lez"
      },
      {
        "assessment": "unknown",
        "currency": "GBP",
        "daily_charge": null,
        "enforcement": "charge",
        "id": "gb-london-ulez",
        "locations_inside": 3,
        "name": "London Ultra Low Emission Zone",
        "operator": "Transport for London",
        "source": "{…}",
        "type": "ulez"
      }
    ],
    "basis": "the optimiser is fed a travel-time matrix, not road geometry, so this block repo…",
    "caveats": "Zone boundaries are simplified from the published geometry to the tolerance stat…",
    "note": "charge depends on vehicle emissions, supply `emissions` (vehicle_category, fuel,…"
  }
}

More from the server's description:

At least one progress entry or one change is required. Requires the MapMap gateway.

plan_day

Turn an itinerary into one navigable multi-stop route. Provide a start and stops (each a location {lat, lon} or a free-text name to geocode, plus optional dwell_minutes time at the stop), optional depart_at (RFC 3339) for absolute ETAs, optimise: true to reorder stops for the shortest day (VROOM solver), and return_to_start.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Inputs

InputTypeRequiredDefaultLimitsDescription
startPlanDayStopInputyesWhere the day starts (name or location; dwell_minutes ignored).
stopsPlanDayStopInput[]yesThe stops to visit (1–20). Visited in the given order unless optimise is true.
costingCostingKindno"auto"Costing model: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
depart_atstringnoDeparture time as RFC 3339 (e.g. "2026-07-18T09:00:00Z"); when given, every ETA is also returned as an absolute timestamp.
optimisebooleannofalseReorder the stops for the shortest day (VROOM solver; requires the optimisation sidecar). Default false: visit in the given order.
return_to_startbooleannofalseEnd the day back at the start (default false).
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck".

PlanDayStopInput: One stop of a plan_day itinerary: an exact location, or a free-text name to geocode (ambiguous matches are flagged in the response, never guessed silently).

InputTypeRequiredDefaultLimitsDescription
dwell_minutesnumberno0Time spent at the stop in minutes (default 0); shifts every later ETA.
locationLatLonnoExact coordinates; skips geocoding.
namestringnoFree-text name or address, geocoded when no location is given.

Output

FieldTypeAlways presentDescription
costingstringyesThe costing the plan was routed with.
finish_offset_snumberyesEnd of the day (last arrival + dwell), seconds after departure.
geometry_polyline6stringyesFull multi-stop route geometry (polyline6), hand it to the map SDK or the route tool consumers directly.
optimisedbooleanyesWhether the stop order was optimised (VROOM) or kept as given.
startPlanStartyesThe day's starting point.
stopsPlannedStop[]yesThe stops in visit order, each with leg, ETAs and any geocoding resolution to double-check.
summarystringyesOne-line human-readable summary of the day.
total_distance_mnumberyesTotal travel distance, metres.
total_dwell_snumberyesTotal time at stops, seconds.
total_travel_duration_snumberyesTotal driving/travel time, seconds.
depart_atstringnoThe departure time echoed back, when one was given.
finish_atstringnoAbsolute end of the day (RFC 3339), when depart_at was given.
return_legReturnLegnoThe leg back to the start, when return_to_start was set.
view_urlstringnoA mapmap.ai link that draws this day plan on a map: open it or pass it on as the last line of the answer. Nothing is stored server-side and opening it needs no key; the link itself carries the stop coordinates and stop names (plus any arrival clocks, dwell times and totals) and nothing else, so anyone holding it can read those stops. Absent when the plan cannot be linked (see view_url_note) or the server runs with links turned off (SN_MCP_SITE_URL empty).
view_url_notestringnoWhy there is no view_url (too many vehicles or stops for one link, or over the 6,000 character cap), or what the link left out to fit under the cap (arrival times, dwell times, then stop names). Relay it with the link.

ReturnLeg: The return leg of a return_to_start plan.

FieldTypeAlways presentDescription
arrival_offset_snumberyesArrival back at the start, seconds after departure.
travel_distance_mnumberyesDistance back to the start, metres.
travel_duration_snumberyesTravel time back to the start, seconds.
arrival_atstringnoAbsolute arrival time (RFC 3339), when depart_at was given.

PlanStart: The day's starting point as resolved.

FieldTypeAlways presentDescription
locationLatLonyesThe start's resolved coordinates.
namestringnoThe start's name, when one was given.
resolutionStopResolutionnoGeocoding details when the start was given as a name.

StopResolution: How a free-text stop name was resolved to coordinates.

FieldTypeAlways presentDescription
alternativesGeocodeHit[]yesUp to three alternative matches, best first.
ambiguousbooleanyesTrue when other plausible matches exist somewhere else, check alternatives and re-run with an explicit location if the chosen one is wrong.
chosenGeocodeHityesThe chosen match (best geocoder hit).
querystringyesThe name that was geocoded.

PlannedStop: One planned stop with its leg and ETAs.

FieldTypeAlways presentDescription
arrival_offset_snumberyesArrival, as seconds after departure from the start.
departure_offset_snumberyesDeparture (arrival + dwell), as seconds after the day's start.
dwell_minutesnumberyesDwell time applied at this stop, minutes.
input_indexintegeryesIndex of this stop in the request's stops array (visit order may differ when optimised).
locationLatLonyesThe stop's resolved coordinates.
travel_distance_mnumberyesDistance of the leg arriving at this stop, metres.
travel_duration_snumberyesTravel time of the leg arriving at this stop, seconds.
arrival_atstringnoAbsolute arrival time (RFC 3339), when depart_at was given.
departure_atstringnoAbsolute departure time (RFC 3339), when depart_at was given.
namestringnoThe stop's name (from the request or the geocoder).
resolutionStopResolutionnoGeocoding details when the stop was given as a name.

Example (called 2026-11-05)

json
{
  "start": {
    "location": {
      "lat": 51.5007,
      "lon": -0.1246
    }
  },
  "stops": [
    {
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      },
      "dwell_minutes": 15
    },
    {
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      }
    }
  ],
  "costing": "auto"
}

Result (trimmed):

json
{
  "costing": "auto",
  "finish_offset_s": 2120.168,
  "geometry_polyline6": "gejfaB~zrFDCRGXC^?rCXvD`@|@H`@J`@\\\\b@P^Pf@Nd@Px@Jp@HfA@fAC`Aq@fKeA|OK`ASlAY|@_@r…",
  "optimised": false,
  "start": {
    "location": {
      "lat": 51.5007,
      "lon": -0.1246
    }
  },
  "stops": [
    {
      "arrival_offset_s": 917.918,
      "departure_offset_s": 1817.9180000000001,
      "dwell_minutes": 15,
      "input_index": 0,
      "location": {
        "lat": 51.5308,
        "lon": -0.1238
      },
      "travel_distance_m": 5106,
      "travel_duration_s": 917.918
    },
    {
      "arrival_offset_s": 2120.168,
      "departure_offset_s": 2120.168,
      "dwell_minutes": 0,
      "input_index": 1,
      "location": {
        "lat": 51.5194,
        "lon": -0.127
      },
      "travel_distance_m": 1724,
      "travel_duration_s": 302.25
    }
  ],
  "summary": "2 stops, 6.8 km, 20 min travelling + 15 min at stops",
  "total_distance_m": 6830,
  "total_dwell_s": 900,
  "total_travel_duration_s": 1220.168,
  "view_url": "https://mapmap.ai/view?p=eyJ2IjoxLCJraW5kIjoicGxhbiIsImNvc3RpbmciOiJhdXRvIiwicm9…"
}

More from the server's description:

Costing "auto", "truck" (with a truck profile the whole day respects dimensional/ADR restrictions), "bicycle", "pedestrian" or "motor_scooter". Returns the stops in visit order with per-leg duration/distance and arrival/departure times, totals, and the full route geometry (polyline6). Geocoded names carry a resolution, when ambiguous is true, check alternatives and re-run with an explicit location rather than trusting the guess. Returns view_url, a mapmap.ai link that draws the answer on a map (no key needed); the link carries the stop coordinates and names.

plan_errands

Order a handful of errands against a hard arrival time, and say honestly whether they fit.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Resolve a named place with geocode first and pass the coordinate: never invent one.
  • The server chooses the order and the actual shops, exhaustively, over engine-computed travel times -- do NOT attempt the ordering or the arithmetic yourself.
  • IMPORTANT: feasible: false is an ANSWER, not an error to retry.
  • Every stop is a real indexed place carrying its id: never mention a shop the answer did not return.
  • hours is "open_on_the_tag" (the map's tag covers your arrival, which is evidence and not a promise) or "unknown" (most places carry no hours at all); a place the tag proves shut is never proposed.
  • ALWAYS show usage_note: this is planned before setting off or by a passenger, never at the wheel.

Inputs

InputTypeRequiredDefaultLimitsDescription
arrive_bystringyesThe hard arrival time, RFC 3339 with an offset ("2026-09-21T15:15:00+01:00"). Convert "quarter past three" to an absolute instant in the driver's own offset before calling.
destinationLatLonyesWhere they have to be by arrive_by.
errandsErrandSpec[]yesThe errands, in any order: the answer decides the order. One to five.
originLatLonyesWhere the driver sets off from.
depart_atstringnonowWhen they set off, RFC 3339. Defaults to now.
destination_namestringnoWhat to call the destination in the answer ("the school").
max_detour_minutesnumberno12, at most 45How far off the direct route a candidate place may sit, as a detour in minutes (default 12, at most 45).

ErrandSpec: One errand in a plan_errands chain: a kind of place to find, or a place the caller already knows.

InputTypeRequiredDefaultLimitsDescription
categorystringnoThe KIND of place wanted: a category from list_place_categories ("pharmacy", "fuel", "supermarket"), or a colloquial phrase the server normalises ("petrol station", "chemist"). Use this for anything the driver described by what it is rather than by name.
dwell_minutesnumberno5How long the driver is out of the car here, minutes (default 5).
idstringnothe categoryWhat to call this errand in the answer ("prescription", "petrol"). Defaults to the category, or to "stop N".
placeLatLonnoA specific place the driver already knows, as a coordinate. Use this for "the school", "the nursery", "Mum's": places the map cannot be expected to resolve from the words alone. Resolve a NAMED place with geocode first and pass the coordinate here; never guess one.

Output

FieldTypeAlways presentDescription
arrive_bystringyesThe deadline, echoed.
corridor_notestringyesAlways present. What the corridor search did and did not look at. An empty answer is a statement about this index and this corridor, never about what exists on the ground.
departurestringyesWhen the driver sets off, RFC 3339.
droppedstring[]yesErrands that had to be dropped for the plan to fit. Empty when feasible.
feasiblebooleanyesWhether every errand fits before the deadline. False is an ANSWER, not an error: report reason, the errand named in blocking_errand, and what dropped says has to go.
opening_hours_notestringyesAlways present. How opening hours were used, and how little of the map carries them.
stopsErrandStop[]yesThe stops, in visit order. On an infeasible answer these are the stops of the reduced plan, the one that DOES fit.
usage_notestringyesAlways present. Why this is planned stationary rather than at the wheel. Show it.
verification_notestringyesAlways present. That every stop is an indexed place or a supplied coordinate, never a generated one.
arrivalstringnoWhen the plan arrives, RFC 3339. Present whenever there is a plan, including the reduced plan on an infeasible answer.
attributionstringnoCredit owed for the place data, when the answer used the index.
blocking_errandstringnoThe errand that costs the most time, when the deadline is missed.
failed_errandstringnoThe errand a "no_candidates_in_corridor" or "all_candidates_closed" answer is about.
geometry_polyline6stringnoThe planned journey as an encoded polyline6.
over_by_snumbernoHow late the whole chain would be, seconds.
reasonstringnoThe same cause in plain language, for the driver.
reason_codestringnoMachine token for why the chain does not fit: "deadline_missed", "destination_unreachable_in_time", "no_candidates_in_corridor", "all_candidates_closed" or "unroutable".
slack_snumbernoSpare seconds before the deadline.
total_drive_snumbernoTotal driving time, seconds.
total_dwell_snumbernoTotal time out of the car, seconds.

ErrandStop: One planned stop in an errand chain.

FieldTypeAlways presentDescription
arrivestringyesWhen the driver arrives, RFC 3339.
departstringyesWhen they leave, RFC 3339.
drive_snumberyesDriving time of the leg into this stop, seconds.
errandstringyesWhich errand this stop discharges.
hoursstringyesWhat the map's opening-hours tag says about the arrival time: "open_on_the_tag" (the tag covers it, which is evidence and NOT a promise) or "unknown" (no usable tag). A stop the tag proves shut is never proposed, so "closed" never appears here.
kindstringyes"category" for a place the index found, "place" for one the caller supplied.
latnumberyesLatitude.
lonnumberyesLongitude.
hours_tagstringnoThe tag itself, when the map carries one.
idstringnoThe index id, for an indexed place. Its presence is what makes the stop checkable; a stop without one is a coordinate the caller gave.
labelstringnoIts full label, for reading aloud.
namestringnoThe place's name.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  },
  "arrive_by": "2026-11-05T11:57:15Z",
  "errands": [
    {
      "category": "pharmacy"
    }
  ]
}

Result (trimmed):

json
{
  "arrival": "2026-11-05T09:17:32Z",
  "arrive_by": "2026-11-05T11:57:15Z",
  "attribution": "Place data © OpenStreetMap contributors, ODbL.",
  "corridor_note": "Candidates come from the corridor of the direct route. A place further off it th…",
  "departure": "2026-11-05T08:57:15Z",
  "dropped": [],
  "feasible": true,
  "geometry_polyline6": "gejfaB~zrFDCRGXC^?rCXvD`@|@H`@J`@\\\\b@P^Pf@Nd@Px@Jp@HfA@fAC`Aq@fKeA|OK`ASlAY|@_@r…",
  "opening_hours_note": "Opening hours come from OpenStreetMap's `opening_hours` tag, which most places d…",
  "slack_s": 9582.5,
  "stops": [
    {
      "arrive": "2026-11-05T09:05:16Z",
      "depart": "2026-11-05T09:10:16Z",
      "drive_s": 480.8,
      "errand": "pharmacy",
      "hours": "unknown",
      "id": "osm:n13260552152:poi",
      "kind": "category",
      "label": "Kerrs, BLOOMSBURY WAY 41, London, Knepp Wildland, United Kingdom",
      "lat": 51.5173794,
      "lon": -0.1250005,
      "name": "Kerrs"
    }
  ],
  "total_drive_s": 916.8,
  "total_dwell_s": 300,
  "usage_note": "Plan before setting off, or let a passenger do it. Stating a request like this i…",
  "…": "1 more fields"
}

More from the server's description:

This is the "pick up a prescription, get petrol, and be at the school by quarter past three" tool. Give origin, destination (+ destination_name), arrive_by (RFC 3339 with an offset: convert "quarter past three" yourself), optional depart_at, and 1 to 5 errands. Each errand is EITHER a category (a kind of place: "pharmacy", "fuel", "supermarket", or a colloquial phrase the server normalises; use list_place_categories for the vocabulary) OR a place {lat, lon} the driver already knows ("the school", "the nursery"). Optional dwell_minutes per errand (default 5). It names the errand that costs the most (blocking_errand), says how late the whole chain would be (over_by_s), and returns the stops of the chain that DOES fit with dropped naming what had to go. Tell the driver what to drop. Requires the MapMap gateway.

plan_ev_route

Plan a whole electric-vehicle journey, charge stops included. Give origin and destination (plus optional waypoints) and a vehicle, a published profile ("small_hatch", "saloon", "suv", "van") and/or inline figures (battery_kwh, mass_kg, drag_area_m2, aux_kw, connectors), with start_soc (default 0.9), min_arrival_soc (default 0.1), reserve_soc (default 0.1, the floor the charge must never drop below mid-route), optional connectors and min_kw filters and ambient_temperature_c.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Energy comes from a published road-load physics model over the route's own legs; charge times are integrated over the vehicle's charging curve capped by the charge point, NOT energy divided by peak power, which is the single biggest error in naive EV planners.
  • That is an ANSWER, not an error to retry: report the reason and never describe it as a plan.
  • gradient_data says whether elevation was available: "absent" means consumption was modelled on the flat and under-reads a hilly route.
  • There is no national charge-point registry, so ALWAYS show the returned coverage_note, an infeasible plan means "none from these operators", never "there are no chargers here", and statuses are current only when availability_live is true.
  • Display the returned charging_attribution with the plan.

Inputs

InputTypeRequiredDefaultLimitsDescription
destinationLatLonyesWhere it ends.
originLatLonyesWhere the journey starts.
ambient_temperature_cnumbernoAmbient temperature in °C. Derates traction energy from a published study; cabin heating belongs in the vehicle's aux_kw.
connectorsstring[]noKeep only charge points offering at least one of these connector standards: "type2", "type1", "ccs", "chademo", "tesla", "domestic", "other". This narrows the vehicle's own set, never widens it.
max_detour_minutesnumberno15, at most 120How far off the route a charge point may sit, as a detour in minutes (default 15, at most 120).
min_arrival_socnumberno0.1Lowest acceptable state of charge on arrival (default 0.1).
min_kwnumbernoKeep only charge points with a usable connector rated at least this many kW (e.g. 50 for rapid charging only).
reserve_socnumberno0.1The floor the state of charge must never fall below mid-route (default 0.1). Distinct from the arrival figure.
start_socnumberno0.9State of charge at the start, 0–1 (default 0.9).
vehicleEvVehicleSpecnothe published "saloon" defaultThe vehicle. Omitted ⇒ the published "saloon" default, and the answer says which vehicle it used.
waypointsLatLon[]noIntermediate points the route must pass through, in order (at most 8). Charge stops are inserted around them.

EvVehicleSpec: The vehicle for plan_ev_route: one of the published defaults by name, inline figures, or a named default with inline figures over it.

InputTypeRequiredDefaultLimitsDescription
aux_kwnumbernoAuxiliary load in kW, lights, electronics, cabin conditioning. Winter heating is several times the mild-weather default.
battery_kwhnumbernoGross nominal battery capacity, kWh.
connectorsstring[]noConnectors the vehicle accepts: "type1", "type2", "ccs1", "ccs2", "chademo", "tesla", "gbt".
drag_area_m2numbernoDrag area (drag coefficient × frontal area), m².
mass_kgnumbernoKerb mass, kg.
namestringnoDisplay name for the vehicle in the answer.
profilestringnoprofile: "small_hatch"A published default profile: "small_hatch", "saloon", "suv" or "van". On its own it selects that vehicle; alongside any inline figure below it is the base the figure overrides.
usable_fractionnumbernoFraction of the gross pack the vehicle will actually use, 0–1.

Output

FieldTypeAlways presentDescription
availability_livebooleanyesWhether charge-point statuses came from a live availability feed. A static planner is the default: without a feed, nothing in this answer is a claim about which bays are free right now.
coverage_notestringyesAlways present. What this deployment's charge-point dataset does and does not cover. An infeasible plan means "none from these operators", never "there are no chargers here". Show this alongside the answer.
feasiblebooleanyesWhether the journey is possible at all. False means no plan exists, a charger desert, a connector mismatch, or a gap wider than the car's range. Report the reason and the furthest reachable point; never describe an infeasible answer as a plan.
gradient_datastringyes"complete", "partial" or "absent". "absent" means the deployment had no elevation data and consumption was modelled on the flat, which under-reads a hilly route. Say so rather than presenting the figure as measured.
legsEvPlanLeg[]yesThe driving legs, in order.
soc_traceEvPlanSocPoint[]yesState of charge at every point of the journey.
stopsEvPlanStop[]yesThe charge stops, in visit order. Always empty when feasible is false: a journey that cannot be completed has no stop list.
summaryEvPlanSummaryyesThe plan at a glance.
charging_attributionstringnoAttribution string for the charge-point operators actually used, display it with the plan (a licence obligation).
furthest_reachable_latnumbernoLatitude of that furthest reachable point.
furthest_reachable_lonnumbernoLongitude of that furthest reachable point.
furthest_reachable_positionnumbernoHow far along the route the vehicle can get unaided, 0.0–1.0, when no plan exists.
geometry_polyline6stringnoThe planned journey's geometry as an encoded polyline6, through the charge stops.
profile_sourcestringno"default" when a published profile supplied the figures, "inline" when the caller did.
reasonstringnoThe same cause in plain language, for the user.
reason_codestringnoMachine token for why no plan exists, when none does: "no_chargers_in_corridor", "connector_mismatch", "out_of_range", "dead_end", "below_min_kw", "chargers_unrated", "stop_limit", "dataset_empty", "no_charge_curve" or "unroutable".
route_distance_mnumbernoDistance of the planned route, metres.
route_duration_snumbernoDriving time of the planned route, seconds.
vehiclestringnoThe vehicle the plan was computed for.

EvPlanLeg: One driving leg of the plan, with its state-of-charge bookkeeping.

FieldTypeAlways presentDescription
consumed_whnumberyesBattery energy the leg spends, Wh.
distance_mnumberyesEngine-computed distance, metres.
duration_snumberyesEngine-computed driving time, seconds.
end_socnumberyesState of charge at the end.
fromstringyesWhere the leg starts: "origin" or a charge point's id.
regen_whnumberyesBattery energy regenerative braking gives back, Wh.
start_socnumberyesState of charge at the start of the leg.
tostringyesWhere it ends: a charge point's id or "destination".
min_socnumbernoLowest state of charge anywhere within the leg.

EvPlanSocPoint: One point on the state-of-charge trace.

FieldTypeAlways presentDescription
along_route_positionnumberyesHow far along the route this point sits, 0.0–1.0.
atstringyes"origin", a charge point's id, or "destination".
socnumberyesState of charge on arrival at this point.
departing_socnumbernoState of charge on leaving, at a charge stop.

EvPlanStop: One charge stop in the plan.

FieldTypeAlways presentDescription
along_route_positionnumberyesHow far along the route this stop sits, 0.0–1.0.
arrive_socnumberyesState of charge on arrival. Never below the reserve floor.
charge_snumberyesTime plugged in, seconds, integrated over the vehicle's charging curve capped by the charge point, not energy ÷ peak power.
charger_idstringyesOperator-scoped stable id.
dcbooleanyesWhether the connector delivers DC (rapid) rather than AC.
depart_socnumberyesState of charge on departure.
latnumberyesWGS84 latitude in decimal degrees.
lonnumberyesWGS84 longitude in decimal degrees.
off_route_mnumberyesStraight-line offset from the route geometry, metres.
sourcestringyesWhich operator feed this came from.
status_livebooleanyesWhether the status came from a live feed rather than the last ingest.
available_nowbooleannoWhether a bay is free right now. Present only where a live availability feed backs the claim, absent means unknown, never "occupied".
charger_kwnumbernoThat connector's rated power, kW, what the charge time was computed against.
connector_standardstringnoThe connector the plan charges on.
detour_snumbernoExtra driving time this stop costs against going straight past it, seconds.
namestringnoSite name, where the operator publishes one.
operatorstringnoOperator display name.
power_kw_sourcestringno"declared" when the operator published the rating, "derived" when it was computed from voltage × amperage × phases. Never present the two as the same thing to a user.

EvPlanSummary: The plan at a glance.

FieldTypeAlways presentDescription
energy_kwhnumberyesNet battery energy the journey costs, kWh.
start_socnumberyesState of charge the journey starts at.
stopsintegeryesHow many charge stops the plan contains. Zero on a journey the car makes on its own, and zero on an infeasible one.
total_charge_snumberyesTime spent plugged in, seconds.
total_distance_mnumberyesDistance of the planned journey, metres.
total_drive_snumberyesTime spent driving, seconds.
total_duration_snumberyesDriving plus charging, seconds, the number a user cares about.
arrival_socnumbernoState of charge on arrival, when the journey is feasible.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5074,
    "lon": -0.1278
  },
  "destination": {
    "lat": 52.4862,
    "lon": -1.8904
  },
  "vehicle": {
    "profile": "saloon"
  }
}

Result (trimmed):

json
{
  "availability_live": false,
  "coverage_note": "Covers 6050 charge point(s) (6052 charging position(s)) from char.gy (on-street …",
  "feasible": true,
  "geometry_polyline6": "kpwfaBx|xFQU]i@]o@_@iA]yAWuBSsBM_D@sCLuBXyA^}Ap@cBb@aAb@m@^]PGb@CfANnA\\x@\\x@r@~A…",
  "gradient_data": "complete",
  "legs": [
    {
      "consumed_wh": 21581.3,
      "distance_m": 189635,
      "duration_s": 8291.3,
      "end_soc": 0.523,
      "from": "origin",
      "min_soc": 0.523,
      "regen_wh": 68.7,
      "start_soc": 0.9,
      "to": "destination"
    }
  ],
  "profile_source": "default",
  "route_distance_m": 189635,
  "route_duration_s": 8291.3,
  "soc_trace": [
    {
      "along_route_position": 0,
      "at": "origin",
      "soc": 0.9
    },
    {
      "along_route_position": 1,
      "at": "destination",
      "soc": 0.523
    }
  ],
  "stops": [],
  "summary": {
    "arrival_soc": 0.523,
    "energy_kwh": 21.51,
    "start_soc": 0.9,
    "stops": 0,
    "total_charge_s": 0,
    "total_distance_m": 189635,
    "total_drive_s": 8291.3,
    "total_duration_s": 8291.3
  },
  "vehicle": "saloon (Tesla Model 3 RWD, Highland)"
}

More from the server's description:

Returns the stops with arrive/depart state of charge, charge time and detour, a per-leg state-of-charge trace, and the journey's driving and charging time. IMPORTANT: when no plan exists, a charger desert, a connector mismatch, a gap wider than the car's range, the answer comes back with feasible: false, a reason and the furthest point on the route the car can actually reach. Requires the MapMap gateway; answers a clear error when the deployment has no charge-point dataset.

Places, fuel and charge points along a route, each priced by its real detour.

search_along_route

Find places (POIs) along a route with the REAL extra travel time of stopping at each, never a straight-line guess.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Inputs

InputTypeRequiredDefaultLimitsDescription
querystringyesFree-text POI query, e.g. "coffee", "EV charger", "truck stop".
categorystringnoExplicit place category ("fuel", "cafe", "charging_station", same vocabulary as nearby_places). Colloquial phrases are normalised server-side; prefer this over query for category intents.
costingCostingKindno"auto"Costing model for the route and detour matrix: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
destinationLatLonnoRoute destination.
geometry_polyline6stringnoAn existing route geometry as an encoded polyline6 (the route tool's geometry_polyline6). Provide either this or origin + destination, not both.
max_detour_minutesnumberno10, at most 120Largest acceptable detour in minutes (default 10, at most 120).
max_resultsintegerno5, at most 25Maximum results (default 5, at most 25).
originLatLonnoRoute origin (with destination, when no geometry is given).
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; the detours then respect dimensional/ADR restrictions.

Output

FieldTypeAlways presentDescription
candidate_capintegeryesThe matrix fan-out cap in force.
candidates_consideredintegeryesCandidates found near the corridor before pricing.
candidates_costedintegeryesCandidates actually priced through the engine (fan-out is capped at candidate_cap nearest-to-route).
costingstringyesThe costing the detours were priced with.
max_detour_minutesnumberyesThe detour budget applied, minutes.
querystringyesThe query as interpreted.
resultsAlongRouteHit[]yesPlaces within the detour budget, cheapest detour first.
route_length_mnumberyesLength of the route geometry in metres.
fuel_attributionstringnoAttribution string for fuel-price data sources, present only when at least one returned result carries fuel_prices (gateway-preferred mode with SN_FUEL_PRICES configured; see AlongRouteHit).
matched_categoriesstring[]noThe normalised category tokens the candidates were browsed by, present only when a category browse actually ran (an explicit category, or a query the server promoted to one). Absent means free-text name matching answered the call, so a caller can tell how its words were understood rather than inferring it from the results.
route_distance_mnumbernoDirect origin→destination distance in metres.
route_duration_snumbernoDirect origin→destination travel time in seconds (same estimator as the detour legs), when routable.

AlongRouteHit: One place found along the route, with its honest detour cost.

FieldTypeAlways presentDescription
along_route_positionnumberyesWhere along the route the place sits, 0.0 (origin) to 1.0 (destination), by distance along the geometry.
detour_minutesnumberyesExtra travel time of visiting this place, in minutes (rounded to 0.1): (origin→place) + (place→destination) − (origin→destination), all computed by the routing engine with the request's costing.
detour_snumberyesThe same detour in raw seconds.
off_route_mnumberyesStraight-line distance from the place to the route, metres.
placeGeocodeHityesThe place (geocoder hit: name, label, coordinates, type).
detour_kmnumbernoExtra travel distance in kilometres, when the engine reported distances.
fuel_brandstringnoThe matched fuel station's brand, when known (same conditions as fuel_prices).
fuel_pricesany JSONnoLive pump prices for this fuel/petrol station, keyed by fuel code (e.g. "diesel", "petrol_95"), each {value, currency, updated_at}. Only ever present when the server is gateway-preferred AND the deployment configured SN_FUEL_PRICES AND this result matched a station within range, never fabricated. Direct-backend fallback never sets this (see the gap this closes in the PR description).
fuel_updated_atstringnoWhen the matched fuel station's prices were last refreshed (RFC 3339), when known (same conditions as fuel_prices).

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  },
  "query": "cafe",
  "category": "cafe"
}

Result (trimmed):

json
{
  "candidate_cap": 25,
  "candidates_considered": 42,
  "candidates_costed": 25,
  "costing": "auto",
  "matched_categories": [
    "cafe"
  ],
  "max_detour_minutes": 10,
  "query": "cafe",
  "results": [
    {
      "along_route_position": 0.776,
      "detour_km": 0,
      "detour_minutes": 0,
      "detour_s": 0,
      "off_route_m": 13,
      "place": {
        "label": "Cafe Angel, London, City of London, Knepp Wildland, United Kingdom",
        "lat": 51.524764100000006,
        "lon": -0.1161125,
        "name": "Cafe Angel",
        "type": "cafe"
      }
    },
    {
      "along_route_position": 0.567,
      "detour_km": 0,
      "detour_minutes": 0,
      "detour_s": 0,
      "off_route_m": 73,
      "place": {
        "label": "Espresso House, London, Knepp Wildland, United Kingdom",
        "lat": 51.51825,
        "lon": -0.120913,
        "name": "Espresso House",
        "type": "cafe"
      }
    },
    "… 3 more"
  ],
  "route_distance_m": 5105,
  "route_duration_s": 917.823,
  "route_length_m": 5098
}

More from the server's description:

Provide origin + destination (a route is computed) or an existing route's geometry_polyline6, plus a free-text query ("coffee", "EV charger", "truck stop") and max_detour_minutes (default 10). For a category intent ("fuel", "EV charger", "coffee") pass category instead of relying on words alone: it takes the same vocabulary as nearby_places (lowercased OSM tag values such as "fuel", "cafe", "charging_station", "parking", "pharmacy"), and common colloquial phrases are normalised server-side ("petrol station" and "gas station" to fuel, "coffee" to cafe, "EV charger" to charging_station). query alone also promotes a pure category phrase to the same browse, so "fuel" finds fuel stations rather than places whose NAME starts "Ful"; anything else stays free-text name matching. When a browse ran, the response echoes the tokens used in matched_categories. Candidates near the route corridor are priced through the routing engine with your costing: detour = (origin→place) + (place→destination) − (origin→destination). Costing "auto", "truck" (with a truck profile the detours respect dimensional/ADR restrictions), "bicycle", "pedestrian" or "motor_scooter". Returns results sorted by detour with detour_minutes, detour_km, along_route_position (0-1) and off_route_m; at most 25 candidates are priced per call (candidate_cap).

cheapest_fuel_along_route

Find the cheapest fuel along a route, with the REAL extra travel time of stopping at each station, never a straight-line guess.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Each result carries price {value, currency, updated_at, stale}, detour_minutes/detour_km, and saving_per_litre vs the cheapest on-route baseline (pass fill_litres to also get saving_total).
  • stale = not verifiably fresher than 24 h: true unless BOTH the price's own updated_at and the snapshot's fetch time are inside that window, and true whenever either is missing.
  • A stale price is ranked below every fresh one, is never the baseline, and carries NO saving_per_litre, quote it as "last seen at X on <date>", never as a saving.
  • say is ALWAYS present and is the whole answer as one short spoken line, composed by the gateway with the number, the detour and the honest qualifier already in it.
  • Prefer reading it verbatim to writing your own summary: a stale price's line states the figure and the day it was last seen and claims no saving, and an empty result's line names the cause rather than implying there is no fuel on that road.
  • coverage says what the price snapshot holds (stations, countries, sources) and, when it holds UK stations, carries a statement naming the UK retailers covered, with uk_retailers and uk_partial.
  • Show the statement with any UK answer: when uk_partial is true UK prices come only from those retailers' feeds, so an empty UK result means "none from these retailers", never "no fuel here".
  • Display the returned fuel_attribution with the prices.

Inputs

InputTypeRequiredDefaultLimitsDescription
costingCostingKindno"auto"Costing model for the route and detour matrix: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
destinationLatLonnoRoute destination.
fill_litresnumbernoOptional fill size in litres; each result then also carries saving_total = saving_per_litre × fill_litres.
fuelstringnoWhich fuel to price: "diesel" (default), "petrol_95", "petrol_98", "premium_diesel", "e85" or "lpg" (aliases "petrol", "unleaded", "e10", "super_unleaded", "e5", "b7" and "sdv" are accepted).
geometry_polyline6stringnoAn existing route geometry as an encoded polyline6 (the route tool's geometry_polyline6). Provide either this or origin + destination, not both.
max_detour_minutesnumberno10, at most 120Largest acceptable detour in minutes (default 10, at most 120).
max_resultsintegerno5, at most 25Maximum results (default 5, at most 25).
originLatLonnoRoute origin (with destination, when no geometry is given).
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; the detours then respect dimensional/ADR restrictions.

Output

FieldTypeAlways presentDescription
baselineFuelBaseline[]yesThe cheapest effectively-on-route option per currency (empty when no station sits on the route itself).
candidate_capintegeryesThe matrix fan-out cap in force.
candidates_consideredintegeryesStations selling the fuel that passed the corridor pre-filter.
candidates_costedintegeryesCandidates actually priced through the engine (fan-out capped at candidate_cap, cheapest kept).
costingstringyesThe costing the detours were priced with.
fuelstringyesThe normalised fuel code that was priced (e.g. "diesel").
max_detour_minutesnumberyesThe detour budget applied, minutes.
resultsFuelStationHit[]yesStations within the detour budget, cheapest first (price, then detour).
route_length_mnumberyesLength of the route geometry in metres.
saystringyesAlways present. The whole answer as one short spoken line, composed by the gateway: the number, the detour, and the honest qualifier where one applies. Safe to read to a driver verbatim, and safe on an empty result, where it names the cause rather than implying there is no fuel on the road. A stale price's line states the figure and the day it was last seen and claims no saving.
coverageFuelCoveragenoWhat the deployment's price snapshot covers. On a UK route, show coverage.statement to the user alongside the results: it names the UK retailers covered, and an empty UK result means "none from these retailers", never "no fuel here". Absent when the gateway is too old to report coverage.
fuel_attributionstringnoAttribution string for the fuel-price data sources actually returned, display it with the prices (a licence obligation).
notestringnoWhy results is empty, when it is: the cause, not a bare list. On a UK route where UK prices come only from some retailers' feeds it ends with coverage.statement, so an empty answer is never read as "no fuel here". Absent when there are results.
route_distance_mnumbernoDirect origin→destination distance in metres.
route_duration_snumbernoDirect origin→destination travel time in seconds (same estimator as the detour legs), when routable.

FuelBaseline: The cheapest effectively-on-route option in one currency, what the driver pays by just pulling in without a detour; the reference the per-result savings are computed against. Only a fresh price is eligible, so this is stale: false by construction.

FieldTypeAlways presentDescription
currencystringyesISO 4217 currency this baseline covers.
stalebooleanyesTrue when the baseline price could not be verified fresher than 24 hours.
station_idstringyesThe baseline station's id.
updated_atstringyesThe baseline price's source timestamp, verbatim.
valuenumberyesBaseline price per litre.
brandstringnoThe baseline station's brand, when known.
namestringnoThe baseline station's human label, when known.

FuelCoverage: What the deployment's fuel-price snapshot actually holds, as the gateway reports it.

FieldTypeAlways presentDescription
countriesstring[]yesISO 3166-1 alpha-2 countries the snapshot holds stations for. A country absent here cannot produce results.
sourcesmap of integeryesFeed code to station count (e.g. "uk", "uk-fuelfinder", "fr", "de").
stationsintegeryesStations in the whole snapshot.
statementstringnoOne sentence naming where UK prices come from and which retailers are in. Show it to the user with any UK answer: when uk_partial is true, an empty UK result means "none from these retailers", never "no fuel here". Present only when the snapshot holds UK stations.
uk_partialbooleannoTrue when UK prices come only from the retailer feeds of the closed CMA interim scheme, which cover some retailers and not others, rather than from the statutory Fuel Finder. Present only when the snapshot holds UK stations.
uk_retailersFuelRetailer[]noThe UK retailers in the snapshot, largest first. Present only when the snapshot holds UK stations.

FuelRetailer: One UK retailer (or, with Fuel Finder loaded, forecourt brand) in the deployment's fuel-price snapshot.

FieldTypeAlways presentDescription
namestringyesRetailer or brand display name, as the gateway names it.
stationsintegeryesHow many stations it contributes to the snapshot.

FuelStationHit: One fuel station along the route, priced with its honest detour.

FieldTypeAlways presentDescription
along_route_positionnumberyesWhere along the route the station sits, 0.0 (origin) to 1.0 (destination).
detour_minutesnumberyesExtra travel time of refuelling here, in minutes (rounded to 0.1): (origin→station) + (station→destination) − (origin→destination), all computed by the routing engine, never a straight-line guess.
detour_snumberyesThe same detour in raw seconds.
latnumberyesWGS84 latitude in decimal degrees.
lonnumberyesWGS84 longitude.
off_route_mnumberyesStraight-line distance from the station to the route, metres.
priceFuelPriceQuoteyesThe requested fuel's live pump price at this station.
sourcestringyesWhich national feed the station came from ("uk-fuelfinder", "uk", "fr", "de").
station_idstringyesSource-scoped stable station id.
brandstringnoBrand where the feed carries one.
detour_kmnumbernoExtra travel distance in kilometres, when the engine reported distances.
namestringnoHuman label: trading name, town or address, where the feed carries one.
saving_per_litrenumbernoPer-litre saving against the cheapest effectively-on-route station in the same currency (negative = dearer than staying on route). Absent when no FRESH station sits on the route itself in this currency, and always absent on a stale result: an unverifiable price may state a number but never claim a saving.
saving_totalnumbernosaving_per_litre × the request's fill_litres, when both exist.

FuelPriceQuote: One live pump price: per-litre value in an ISO 4217 currency, with the source's own update timestamp and a 24-hour staleness flag.

FieldTypeAlways presentDescription
currencystringyesISO 4217 code (GBP for the UK sources, EUR for FR/DE).
stalebooleanyesTrue when the price could not be verified fresher than 24 hours: unless BOTH the source's own updated_at and the snapshot's fetch time fall inside that window, and always when either is missing. Treat it as indicative, not bindable, say "last seen at X on <date>", never quote it as today's price or as a saving.
updated_atstringyesThe source's own price timestamp, verbatim.
valuenumberyesPrice per litre in currency.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  }
}

Result (trimmed):

json
{
  "baseline": [],
  "candidate_cap": 25,
  "candidates_considered": 66,
  "candidates_costed": 25,
  "costing": "auto",
  "coverage": {
    "countries": [
      "DE",
      "FR",
      "… 1 more"
    ],
    "sources": {
      "de": 1907,
      "fr": 9824,
      "uk": 1919
    },
    "statement": "UK prices come only from the feeds of 8 retailers: Asda (790), Shell (546), Ront…",
    "stations": 13650,
    "uk_partial": true,
    "uk_retailers": [
      {
        "name": "Asda",
        "stations": 790
      },
      {
        "name": "Shell",
        "stations": 546
      },
      "… 6 more"
    ]
  },
  "fuel": "diesel",
  "fuel_attribution": "Fuel prices: UK retailer scheme (CMA)",
  "max_detour_minutes": 10,
  "results": [
    {
      "along_route_position": 0.922,
      "brand": "ESSO",
      "detour_km": 2.21,
      "detour_minutes": 6.2,
      "detour_s": 372,
      "lat": 51.537939,
      "lon": -0.116776,
      "name": "219-227, CALEDONIAN RD, LONDON",
      "off_route_m": 887,
      "price": {
        "currency": "GBP",
        "stale": false,
        "updated_at": "05/11/2026 00:05:14",
        "value": 2.009
      },
      "source": "uk",
      "station_id": "esso:gcpvjq4yqbjt"
    },
    {
      "along_route_position": 0,
      "brand": "SHELL",
      "detour_km": 2.71,
      "detour_minutes": 7.6,
      "detour_s": 456,
      "lat": 51.503381,
      "lon": -0.095515,
      "name": "101-103 Southwark Bridge Road",
      "off_route_m": 2054,
      "price": {
        "currency": "GBP",
        "stale": true,
        "updated_at": "13/05/2026 00:00:00",
        "value": 1.849
      },
      "source": "uk",
      "station_id": "shell:gcpuvzfkyq4f"
    }
  ],
  "route_distance_m": 5106,
  "route_duration_s": 917.918,
  "route_length_m": 5098,
  "say": "Cheapest diesel is Esso at 200.9p a litre, and it adds six minutes. Nothing sits…"
}

More from the server's description:

Provide origin + destination (a route is computed) or an existing route's geometry_polyline6, plus a fuel code ("diesel" default, "petrol_95", "petrol_98", "premium_diesel", "e85", "lpg") and max_detour_minutes (default 10). Stations come from the live open-data price feeds (UK retailer feeds of the closed CMA interim scheme and/or the statutory Fuel Finder, FR prix-carburants, DE Tankerkoenig; the response's fuel_attribution names the ones actually matched), are priced through the routing engine with your costing (a truck profile makes detours respect dimensional/ADR restrictions) and ranked freshest-priced first and cheapest within that. An empty result also carries note, the cause in prose, which on such a route ends with that statement. Requires the MapMap gateway; answers a clear error when the deployment has no fuel-price dataset.

cheapest_charging_along_route

Find the best EV charge points along a route, with the REAL extra travel time of stopping at each one, never a straight-line guess.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Provide origin + destination (a route is computed) or an existing route's geometry_polyline6, plus optional connectors ("ccs", "type2", "chademo", "type1", "tesla", "domestic", "other"), min_kw (e.g. 50 for rapid only), available_only and max_detour_minutes (default 10).
  • IMPORTANT: there is no national charge-point registry, every deployment covers only the operators it has onboarded, so ALWAYS show the returned coverage_note alongside the results.
  • An empty results means "none from these operators within the detour budget", NEVER "there are no chargers here".
  • Statuses are live only when availability_live is true; otherwise they are the values captured at the last ingest and must not be described as current.
  • say is ALWAYS present and is the whole answer as one short spoken line, composed by the gateway with the coverage already inside the claim rather than appended to it: it scopes its superlative to the operators this deployment holds, and an empty result's line says the emptiness is about those operators.
  • Display the returned charging_attribution with the results.

Inputs

InputTypeRequiredDefaultLimitsDescription
available_onlybooleannoKeep only charge points with a bay reported free right now. Needs the deployment to have a live availability feed; without one the call is refused rather than silently returning nothing.
connectorsstring[]noevery standardKeep only charge points offering at least one of these connector standards: "type2", "type1", "ccs", "chademo", "tesla", "domestic" or "other". Omitted ⇒ every standard.
costingCostingKindno"auto"Costing model for the route and detour matrix: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
destinationLatLonnoRoute destination.
geometry_polyline6stringnoAn existing route geometry as an encoded polyline6 (the route tool's geometry_polyline6). Provide either this or origin + destination, not both.
max_detour_minutesnumberno10, at most 120Largest acceptable detour in minutes (default 10, at most 120).
max_resultsintegerno5, at most 25Maximum results (default 5, at most 25).
min_kwnumbernoKeep only charge points whose best connector is rated at least this many kW (e.g. 50 for rapid charging only).
originLatLonnoRoute origin (with destination, when no geometry is given).
truckTruckSpecnoTruck profile (dimensions + ADR declaration). Requires costing "truck"; the detours then respect dimensional/ADR restrictions.

Output

FieldTypeAlways presentDescription
availability_livebooleanyesWhether statuses are live (a bring-your-own availability feed) or the values captured at the last ingest.
availability_notestringyesPlain-language explanation of what the statuses mean here.
candidate_capintegeryesThe matrix fan-out cap in force.
candidates_consideredintegeryesCharge points matching the filters that passed the corridor pre-filter.
candidates_costedintegeryesCandidates actually costed through the engine (fan-out capped at candidate_cap, most powerful kept).
costingstringyesThe costing the detours were computed with.
coverage_notestringyesAlways present. What this deployment's charge-point dataset does and does not cover. An empty results means "none from these operators within the budget", never "there are no chargers here". Show this to the user alongside the results.
max_detour_minutesnumberyesThe detour budget applied, minutes.
resultsChargerHit[]yesCharge points within the detour budget, most powerful first (power, then detour).
route_length_mnumberyesLength of the route geometry in metres.
saystringyesAlways present. The whole answer as one short spoken line, composed by the gateway. Coverage is inside the claim rather than appended to it: the line scopes its superlative to the operators this deployment holds, and an empty result's line says the emptiness is about those operators, never about the road. Safe to read to a driver verbatim.
sourcesChargingSource[]yesEvery operator in the dataset, with its own coverage note and licence. Present even when results is empty.
charging_attributionstringnoAttribution string for the charge-point operators actually returned, display it with the results (a licence obligation).
notestringnoWhy results is empty, when it is, the cause, not a bare list.
route_distance_mnumbernoDirect origin→destination distance in metres.
route_duration_snumbernoDirect origin→destination travel time in seconds (same estimator as the detour legs), when routable.

ChargerHit: One charge point along the route, with its real engine-computed detour.

FieldTypeAlways presentDescription
along_route_positionnumberyesHow far along the route the charge point sits, 0.0–1.0.
charger_idstringyesOperator-scoped stable id.
connector_standardsstring[]yesEvery distinct connector standard at the site.
detour_minutesnumberyesReal extra travel time of stopping here, minutes.
detour_snumberyesThe same detour in seconds.
evse_countintegeryesNumber of charging positions (EVSEs) at the site.
latnumberyesWGS84 latitude in decimal degrees.
lonnumberyesWGS84 longitude in decimal degrees.
off_route_mnumberyesStraight-line offset from the route geometry, metres.
sourcestringyesWhich operator feed this came from.
status_livebooleanyesWhether the status came from a live feed rather than the last ingest.
available_nowbooleannoWhether a bay is free right now. Present only where a live availability feed backs the claim, absent means unknown, never "occupied".
best_connectorChargerConnectornoThe highest-rated connector at the site.
detour_kmnumbernoExtra distance of the detour in kilometres, when the engine reported distances.
max_power_kwnumbernoHighest rated power at the site, kW.
namestringnoSite name, where the operator publishes one.
operatorstringnoOperator display name.
updated_atstringnoWhen the operator last updated this record.

ChargerConnector: The highest-rated connector at a charge point.

FieldTypeAlways presentDescription
dcbooleanyesWhether this connector delivers DC (rapid) rather than AC.
standardstringyesNormalised standard: "type2", "type1", "ccs", "chademo", "tesla", "domestic" or "other".
power_kwnumbernoRated power in kW, where the operator publishes enough to know it.
power_kw_sourcestringno"declared" when the operator published the rating, "derived" when it was computed from voltage × amperage × phases. Never present the two as the same thing to a user.

ChargingSource: One operator's coverage and licence, as published by the deployment.

FieldTypeAlways presentDescription
chargersintegeryesHow many charge points this operator contributes.
operatorstringyesOperator display name.
source_idstringyesOperator id.
coverage_notestringnoPlain-language statement of what this operator's feed does and does not cover.
licencestringnoLicence or statutory basis of the feed.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  }
}

Result (trimmed):

json
{
  "availability_live": false,
  "availability_note": "No live availability feed is configured, so every status is the value captured w…",
  "candidate_cap": 25,
  "candidates_considered": 3654,
  "candidates_costed": 25,
  "charging_attribution": "Charge points: Charge-point availability data from char.gy (char.gy), published …",
  "costing": "auto",
  "coverage_note": "Covers 6050 charge point(s) (6052 charging position(s)) from char.gy (on-street …",
  "max_detour_minutes": 10,
  "results": [
    {
      "along_route_position": 0.005,
      "best_connector": {
        "dc": false,
        "power_kw": 5.1,
        "power_kw_source": "derived",
        "standard": "type2"
      },
      "charger_id": "758bc688-03b7-4b59-ab96-38222e565364",
      "connector_standards": [
        "type2"
      ],
      "detour_km": 1.34,
      "detour_minutes": 3.8,
      "detour_s": 226,
      "evse_count": 1,
      "lat": 51.4992747,
      "lon": -0.1090411,
      "max_power_kw": 5.1,
      "name": "12 Morley Street",
      "off_route_m": 1104,
      "operator": "char.gy",
      "…": "3 more fields"
    },
    {
      "along_route_position": 0,
      "best_connector": {
        "dc": false,
        "power_kw": 5.1,
        "power_kw_source": "derived",
        "standard": "type2"
      },
      "charger_id": "20d8c147-706c-42c0-ae40-3a5ec35e9415",
      "connector_standards": [
        "type2"
      ],
      "detour_km": 1.46,
      "detour_minutes": 3.9,
      "detour_s": 236,
      "evse_count": 1,
      "lat": 51.5023477,
      "lon": -0.1066136,
      "max_power_kw": 5.1,
      "name": "Opposite 29 Ufford Street",
      "off_route_m": 1277,
      "operator": "char.gy",
      "…": "3 more fields"
    },
    "… 3 more"
  ],
  "route_distance_m": 5106,
  "route_duration_s": 917.918,
  "route_length_m": 5098,
  "say": "The best charge point we cover is 12 Morley Street, 5 kilowatts, and it adds fou…",
  "…": "1 more fields"
}

More from the server's description:

Charge points come from operator-published feeds, are costed through the routing engine with your costing (a truck profile makes detours respect dimensional/ADR restrictions) and ranked most powerful first, since minutes off the clock are bought with kilowatts. Each result carries max_power_kw, connector_standards, best_connector, evse_count, detour_minutes/detour_km and, where a live feed backs it, available_now. Prefer reading it verbatim to writing your own summary. Requires the MapMap gateway; answers a clear error when the deployment has no charge-point dataset.

Places and geocoding

Addresses to coordinates and back, what is near a point, what is in view, and the category vocabulary.

geocode

Turn a place (address, POI, town) into coordinates. Ask two ways, and they combine: query is free text, one run-together string, the way a person types into a search box, and street, housenumber, city, postcode and country name the parts of an address separately.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Pass the parts whenever you already hold the address in parts (a form, a CRM row, a manifest): components are REQUIREMENTS, not hints, so city: "London" means a result outside London cannot come back at all, where "London" inside query only reorders.
  • country takes an ISO 3166-1 alpha-2 code or a country name ("GB", "United Kingdom"); a value naming no country is refused rather than silently matching nothing.
  • READ IT before acting on an address, an unmatched or inferred postcode on the top hit means the answer does not carry the address you asked for, and a small score_gap means the ranking barely chose, so show the alternatives instead of picking one.

Inputs

InputTypeRequiredDefaultLimitsDescription
citystringnoStructured component: town or city, e.g. "London". Matches the containing city as well as the immediate locality, so a suburb name works here too.
countrystringnoStructured component: ISO 3166-1 alpha-2 code or country name, "GB", "gb", "United Kingdom" and "UK" all mean the same country. A value naming no country is refused rather than quietly applied as a filter that matches nothing.
focusLatLonnoOptional location bias: results near this point rank higher.
housenumberstringnoStructured component: house number, e.g. "10" or "221B". Only meaningful alongside street, a house number on its own excludes nearly everything and identifies nothing.
langstringno"en"Language of the place names to match and return, as a two-letter code. Defaults to "en", which is what makes English exonyms ("Munich", "Cologne", "Geneva") resolve to the place meant rather than a same-named town elsewhere. Set it to the language your query is written in; "default" asks for each place's own local name. Deployments support a fixed set (this one: "en", "de", "fr"), and anything outside it is refused.
limitintegerno10Maximum number of results (1–50, default 10).
postcodestringnoStructured component: postcode in any spacing or case, "SW1A 2AA" and "sw1a2aa" are one query. A bare UK outward code ("SW1A") selects the whole district.
querystringnoFree-text place query, e.g. "Dover ferry terminal". Required unless at least one structured component is supplied.
streetstringnoStructured component: street name, e.g. "Downing Street". Matches the street of addresses and POIs (transliterated street names included) as well as the street itself.

Output

FieldTypeAlways presentDescription
resultsGeocodeHit[]yesMatching places, best first.

Example (called 2026-11-05)

json
{
  "query": "10 Downing Street, London",
  "limit": 2
}

Result (trimmed):

json
{
  "results": [
    {
      "city": "Westminster",
      "country": "United Kingdom",
      "label": "10 Downing Street, Downing Street 10, SW1A 2AA, Westminster, England, United Kingdom",
      "lat": 51.5034878,
      "lon": -0.1276965,
      "match": {
        "components": "{…}",
        "score_gap": 0,
        "source": "photon"
      },
      "name": "10 Downing Street",
      "postcode": "SW1A 2AA",
      "type": "house"
    }
  ]
}

More from the server's description:

At least one of the two is required. Returns up to limit (default 10) candidates with name, one-line label, lat/lon, type and address parts. Pass focus {lat, lon} to rank results near a location higher. A bare city or town name (no comma) lists that city's airports and main railway stations directly behind it, ahead of namesakes elsewhere. Each hit also carries match: a per-component matched/inferred/unmatched verdict, the score_gap to the runner-up, and which backend answered. Use verify_places when the address came from a model or a user and needs checking rather than using. Results are matched in lang (default "en"), so English exonyms ("Munich", "Cologne", "Geneva") resolve to the place meant; pass lang when querying in another language, or "default" for each place's local name.

reverse_geocode

Turn coordinates into the nearest places: addresses, POIs and localities with distance in metres.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Provide lat and lon; returns up to limit (default 5, max 10) results, nearest first, each with name, one-line label, lat/lon, type, address parts and distance_m, plus categories and a details object of display tags (opening_hours, website, phone, wikipedia, ...) on POI hits when the index carries them.
  • Add fov_deg to keep only what lies within that cone of the heading, it is the FULL width, so 90 keeps what lies within 45 degrees either side of dead ahead; anything dropped is counted in out_of_view.

Inputs

InputTypeRequiredDefaultLimitsDescription
latnumberyesLatitude in decimal degrees (−90 to 90).
lonnumberyesLongitude in decimal degrees (−180 to 180).
fov_degnumbernoField of view: the full width in degrees of a cone centred on heading_deg, outside which results are dropped, it is the FULL width, so 90 keeps only what lies within 45 degrees either side of dead ahead. Needs heading_deg, a cone has to point somewhere. The count of results removed is reported as out_of_view.
heading_degnumbernoWhich way the user is facing, in degrees clockwise from true north (0 = north, 90 = east, 180 = south, 270 = west). Supply it and every result is also described from the user's point of view ("just ahead on your right"); omit it and results fall back to cardinal directions ("to the north-east"), so the tool works with or without a compass.
limitintegerno5Maximum number of results (1–10, default 5). The hosted first-party index answers at most 5 nearest hits per lookup.

Output

FieldTypeAlways presentDescription
resultsReverseGeocodeHit[]yesNearby places, nearest first.
out_of_viewintegernoHow many otherwise-matching places were dropped for falling outside fov_deg. Non-zero means there are places near the user that are simply not in front of them, say so rather than reporting nothing nearby. Always 0 when no field of view was set.

ReverseGeocodeHit: One reverse-geocoding result: a place near the queried point. The same shape family as GeocodeHit, plus the reverse-only extras (distance_m, categories, details) and the egocentric extras (bearing_deg, relative_bearing_deg, direction).

FieldTypeAlways presentDescription
labelstringyesHuman-readable one-line label assembled from the address parts.
latnumberyesLatitude of the place in decimal degrees.
lonnumberyesLongitude of the place in decimal degrees.
bearing_degnumbernoBearing from the queried point to this place, degrees clockwise from true north. Always present.
categoriesstring[]noPOI categories (e.g. ["cafe"]), when the index carries them.
citystringnoCity or town, when known.
countrystringnoCountry, when known: a country name, or the ISO 3166-1 alpha-2 code (e.g. "GB") on first-party hits, which carry only the code.
detailsany JSONnoWhitelisted OSM display tags on POI hits (opening_hours, website, phone, brand, cuisine, wheelchair, wikipedia, ...), passed through verbatim when present.
directionstringnoThe direction phrased for speech, "ahead and slightly to your right, about 80 metres" with a heading, "to the north-east, about 80 metres" without one. Deliver this verbatim rather than reading out coordinates.
distance_mnumbernoStraight-line distance from the queried point in metres, when the backend reports one (first-party gateway hits always do).
namestringnoPlace name, when the source feature has one.
postcodestringnoPostcode, when known.
relative_bearing_degnumbernoWhere this place is relative to the way the user is facing: negative to the left, positive to the right, −180 to 180. Present only when the request supplied heading_deg.
typestringnoFeature type, e.g. "address", "street", "poi", "locality".

Example (called 2026-11-05)

json
{
  "lat": 51.5034,
  "lon": -0.1276,
  "limit": 2
}

Result (trimmed):

json
{
  "results": [
    {
      "bearing_deg": 66.25084657315062,
      "categories": [
        "government"
      ],
      "city": "London",
      "country": "United Kingdom",
      "details": {
        "website": "https://www.gov.uk/government/organisations/prime-ministers-office-10-downing-street",
        "wikidata": "Q169101",
        "wikipedia": "en:10 Downing Street"
      },
      "direction": "to the north-east, a few steps away",
      "distance_m": 8,
      "label": "10 Downing Street, Downing Street 10, SW1A 2AA, London, Knepp Wildland, United Kingdom",
      "lat": 51.50342814127604,
      "lon": -0.12749751759510414,
      "name": "10 Downing Street",
      "postcode": "SW1A 2AA",
      "type": "poi"
    },
    {
      "bearing_deg": 261.14739979814834,
      "city": "London",
      "country": "United Kingdom",
      "direction": "to the west, a few steps away",
      "distance_m": 16,
      "label": "SW1A 2AB, London, Knepp Wildland, United Kingdom",
      "lat": 51.50337715148926,
      "lon": -0.1278350606560707,
      "name": "SW1A 2AB",
      "postcode": "SW1A 2AB",
      "type": "postcode"
    }
  ]
}

More from the server's description:

The inverse of geocode. Every result also carries bearing_deg and a spoken direction. Pass heading_deg (degrees clockwise from true north, 0 = north, 90 = east) and results are described from where the user stands, "ahead and slightly to your right, about 80 metres", with a signed relative_bearing_deg (negative left, positive right); without a heading the phrasing falls back to cardinals ("north-east of you"), so this works with or without a compass. Prefer reading direction aloud over coordinates.

nearby_places

Find places near a point, nearest first with distance in metres, by category (cafes, fuel, EV charging, parking), by name or brand ("the nearest Lloyds bank", "nearest Sainsbury's"), or both.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Use this instead of geocode whenever the question is about what is NEAR a location: geocode ranks a brand's branches everywhere and only biases by proximity, so it will happily return a Lloyds in another city over the one 100 m away.
  • A category or name the map does not carry returns an empty list, never an error.
  • Optional radius_m (default 2500, max 100000) bounds the straight-line search distance and limit (default 5, max 10) the result count.
  • Each result has name, one-line label, lat/lon, address parts, distance_m, categories and a details object of display tags (opening_hours, website, phone, ...) when the map carries them.
  • Add fov_deg to keep only what lies within that cone of the heading, it is the FULL width of the cone, so 90 keeps what lies within 45 degrees either side of dead ahead; anything dropped is counted in out_of_view, so a non-zero count means there ARE matching places nearby, just not in front of the user, say that rather than "nothing nearby".

Inputs

InputTypeRequiredDefaultLimitsDescription
latnumberyesLatitude of the search point in decimal degrees (−90 to 90).
lonnumberyesLongitude of the search point in decimal degrees (−180 to 180).
categorystringnoThe kind of place to find. The gateway matches it against the index's lowercased OSM tag values (the value of the POI's amenity/shop/tourism/railway/… tag), e.g. "cafe", "fuel", "charging_station", "parking", "pharmacy", "supermarket", "hotel", "restaurant", "fast_food", "atm", "bakery", "hospital", "station", and normalises common colloquial names first ("coffee" → cafe; "ev_charging", "ev charging" → charging_station; "petrol" → fuel; "chemist" → pharmacy). A category the index does not carry matches nothing: the result is an empty list, not an error. Optional when name is given; at least one of the two is required.
fov_degnumbernoField of view: the full width in degrees of a cone centred on heading_deg, outside which results are dropped, it is the FULL width, so 90 keeps only what lies within 45 degrees either side of dead ahead. Needs heading_deg, a cone has to point somewhere. The count of results removed is reported as out_of_view.
heading_degnumbernoWhich way the user is facing, in degrees clockwise from true north (0 = north, 90 = east, 180 = south, 270 = west). Supply it and every result is also described from the user's point of view ("just ahead on your right"); omit it and results fall back to cardinal directions ("to the north-east"), so the tool works with or without a compass.
limitintegerno5Maximum number of results (1–10, default 5).
namestringnoThe name or brand of the place to find, "Lloyds", "Lloyds Bank", "Sainsbury's", for "where is the nearest X" questions. Every word must appear in the place's name or one of its alternative names, case- and accent-insensitively, with the last word also matching as a prefix ("Sains" finds Sainsbury's). Combine with category to disambiguate a brand used by more than one kind of place ("Lloyds" plus "bank" excludes Lloyds Pharmacy). Optional when category is given; at least one of the two is required.
radius_mintegerno2500Maximum straight-line distance of any result from the point, in metres (1–100000, default 2500).

Output

FieldTypeAlways presentDescription
resultsNearbyPlace[]yesMatching places, nearest first. Empty when the index has no such place within the radius.
out_of_viewintegernoHow many otherwise-matching places were dropped for falling outside fov_deg. Non-zero means there are matching places near the user that are simply not in front of them, say so rather than reporting nothing nearby. Always 0 when no field of view was set.

NearbyPlace: One nearby_places result: a place matching the requested category and/or name near the queried point. The same shape family as GeocodeHit, plus the browse-only extras (distance_m, categories, details) and the egocentric extras (bearing_deg, relative_bearing_deg, direction).

FieldTypeAlways presentDescription
labelstringyesHuman-readable one-line label assembled from the address parts.
latnumberyesLatitude of the place in decimal degrees.
lonnumberyesLongitude of the place in decimal degrees.
bearing_degnumbernoBearing from the queried point to this place, degrees clockwise from true north. Always present.
categoriesstring[]noPOI categories (e.g. ["cafe"]), when the index carries them.
citystringnoCity or town, when known.
countrystringnoCountry, when known: a country name, or the ISO 3166-1 alpha-2 code (e.g. "GB") on first-party hits, which carry only the code.
detailsany JSONnoWhitelisted OSM display tags on POI hits (opening_hours, website, phone, brand, cuisine, wheelchair, wikipedia, ...), passed through verbatim when present.
directionstringnoThe direction phrased for speech, "ahead and slightly to your right, about 80 metres" with a heading, "to the north-east, about 80 metres" without one. Deliver this verbatim rather than reading out coordinates.
distance_mnumbernoStraight-line distance from the queried point in metres.
namestringnoPlace name, when the source feature has one.
postcodestringnoPostcode, when known.
relative_bearing_degnumbernoWhere this place is relative to the way the user is facing: negative to the left, positive to the right, −180 to 180. Present only when the request supplied heading_deg.
typestringnoFeature type, "poi" for category-browse hits.

Example (called 2026-11-05)

json
{
  "lat": 51.5308,
  "lon": -0.1238,
  "category": "cafe",
  "limit": 2
}

Result (trimmed):

json
{
  "results": [
    {
      "bearing_deg": 204.71648510765286,
      "categories": [
        "cafe"
      ],
      "city": "London",
      "country": "United Kingdom",
      "details": {
        "brand": "Costa",
        "cuisine": "coffee_shop",
        "opening_hours": "Mo-Fr 06:00-21:00, Sa,Su 07:00-20:00",
        "outdoor_seating": "no",
        "takeaway": "yes",
        "wheelchair": "limited"
      },
      "direction": "to the south-west, about 100 metres",
      "distance_m": 103,
      "label": "Costa, London, Knepp Wildland, United Kingdom",
      "lat": 51.52996,
      "lon": -0.1244199,
      "name": "Costa",
      "type": "poi"
    },
    {
      "bearing_deg": 206.64196720540315,
      "categories": [
        "cafe"
      ],
      "city": "London",
      "country": "United Kingdom",
      "details": {
        "cuisine": "coffee_shop;turkish",
        "outdoor_seating": "yes"
      },
      "direction": "to the south-west, about 380 metres",
      "distance_m": 383,
      "label": "Sandwich Street Kitchen, London, Knepp Wildland, United Kingdom",
      "lat": 51.5277186,
      "lon": -0.1262783,
      "name": "Sandwich Street Kitchen",
      "type": "poi"
    }
  ]
}

More from the server's description:

Provide lat, lon and at least one of category or name. category is matched against the map's lowercased OSM tag values (amenity/shop/tourism/…), e.g. "cafe", "fuel", "charging_station", "parking", "pharmacy", "supermarket", "hotel", "restaurant", "fast_food", "atm", "bakery", "hospital", "station"; common colloquial names are normalised ("coffee" -> cafe, "ev_charging" -> charging_station, "petrol" -> fuel, "chemist" -> pharmacy). name matches the place's name or alternative names word by word, case- and accent-insensitively, with the last word also matching as a prefix. Combine the two to disambiguate a brand, "Lloyds" plus "bank" excludes Lloyds Pharmacy. Every result also carries bearing_deg and a spoken direction. Pass heading_deg (degrees clockwise from true north, 0 = north, 90 = east) and results are described from where the user stands, "ahead and slightly to your right, about 80 metres", with a signed relative_bearing_deg (negative left, positive right); without a heading the phrasing falls back to cardinals ("to the north-east"), so this works with or without a compass. Prefer reading direction aloud over coordinates. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).

places_in_view

What is over THERE: given a position and the direction the user is facing, the places inside that cone, and whether the ground and the buildings in between let them actually be seen.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • READ THE VISIBILITY BLOCK BEFORE SAYING ANYTHING: clear means nothing in the data stands in the way, occluded names what does (a hill, or a building with its height), and unknown means NO check could run: with unknown say you cannot tell, never that it is visible.
  • Read basis too: terrain-only in a town means hills were checked and buildings were not, which is weak.
  • A non-zero out_of_sector means there ARE matching places nearby, just not in that direction, so say that rather than "nothing nearby".
  • coverage names the datasets that answered and caveat is the standing limit: visibility is modelled from maps, not observed, and trees, walls, scaffolding and weather are not in it.

Inputs

InputTypeRequiredDefaultLimitsDescription
bearing_degnumberyesWhich way the observer is facing, in degrees clockwise from true north (0 = north, 90 = east, 180 = south, 270 = west). Required: this tool answers "what is over there", and "there" has to point somewhere.
latnumberyesLatitude of the observer in decimal degrees (−90 to 90).
lonnumberyesLongitude of the observer in decimal degrees (−180 to 180).
categorystringnoThe kind of place to look for, from the same vocabulary nearby_places uses ("cafe", "pub", "fuel", "charging_station" …). Use "building" to ask what a named building over there is. At least one of category and name is required.
eye_height_mnumberno1Height of the observer's eye above the ground in metres (0–500, default 1.6, a standing adult). A driver's eye is nearer 1.2 m.
fov_degnumberno60Full width in degrees of the cone centred on bearing_deg (1–360, default 60, so 30 degrees either side). The FULL width, not the half angle.
limitintegerno5Maximum number of results (1–25, default 5).
namestringnoA place name or brand to look for. At least one of category and name is required.
radius_mintegerno1000Maximum straight-line range in metres (1–5000, default 1000).
visible_onlybooleannofalseWhen true, return only places whose visibility verdict is clear. Default false, which returns everything in the cone WITH its verdict, usually the better answer, because "it is there but you cannot see it from here" is worth saying.

Output

FieldTypeAlways presentDescription
caveatstringyesThe standing caveat: visibility here is modelled, not observed.
coveragestringyesOne sentence naming which datasets the visibility verdicts were actually checked against. Worth repeating when a verdict is being relied on.
resultsPlaceInView[]yesPlaces inside the cone, best first: everything visible before everything unchecked before everything occluded, and within each of those, nearest and most nearly dead-ahead first.
out_of_sectorintegernoHow many otherwise-matching places were dropped for lying outside the cone. Non-zero means there ARE matching places near the observer, just not in the direction they are facing, so say that rather than "nothing nearby".

PlaceInView: One place in the observer's field of view.

FieldTypeAlways presentDescription
angular_offset_degnumberyesSigned angle from the observer's bearing to the place: negative to the left, positive to the right.
bearing_degnumberyesThe place's own bearing from the observer, degrees clockwise from true north.
directionstringyesThe direction phrased for speech, from where the observer stands ("ahead and slightly to your right, about 80 metres"). Prefer reading this aloud over coordinates.
distance_mnumberyesStraight-line distance from the observer, in metres.
labelstringyesOne-line label: name, street, postcode, town, country.
latnumberyesLatitude in decimal degrees.
lonnumberyesLongitude in decimal degrees.
namestringyesPlace name.
visibilityViewVisibilityyesWhether it can be seen, and what that rests on.
categoriesstring[]noThe place's categories as the map tags them.

ViewVisibility: Whether a place can be seen from the observer, and what that rests on.

FieldTypeAlways presentDescription
basisstringyesWhat the verdict rests on: terrain-and-buildings, terrain-only, buildings-only or nothing. terrain-only in a town is weak: it means hills were checked and buildings were not.
buildingsstringyesWhat the building check concluded: clear, occluded, no-building-data or beyond-building-range.
terrainstringyesWhat the terrain check concluded: clear, occluded or no-elevation-data.
verdictstringyesclear (nothing in the data stands in the way), occluded (something does, and it is named), or unknown (no check could run, so this result carries NO visibility claim: say so rather than implying either).
obstructionViewObstructionnoThe obstruction, present exactly when the verdict is occluded.

ViewObstruction: What stands in the way of seeing a place.

FieldTypeAlways presentDescription
distance_mnumberyesHow far along the sight line it stands, in metres.
height_above_sightline_mnumberyesHow far its top rises above the sight line, in metres.
kindstringyesterrain or building.
building_height_basisstringnotagged when somebody mapped the building's height, or schema-default when nobody did and the map's flat 5 m fallback was used. A schema-default obstruction is a weaker finding and should be described as one.
building_height_mnumbernoHeight of the blocking building above its own ground, in metres. Absent for terrain.
building_namestringnoThe blocking building's name, when the map carries one.

Example (called 2026-11-05)

json
{
  "lat": 51.5007,
  "lon": -0.1246,
  "bearing_deg": 0,
  "category": "cafe",
  "limit": 2
}

Result (trimmed):

json
{
  "caveat": "Visibility is modelled, not observed. It is a straight sight line over sampled b…",
  "coverage": "Visibility was checked against both the bare-earth elevation model and OpenStree…",
  "out_of_sector": 73,
  "results": [
    {
      "angular_offset_deg": -1.4,
      "bearing_deg": 358.6,
      "categories": [
        "cafe"
      ],
      "direction": "directly ahead, about 50 metres",
      "distance_m": 46.7,
      "label": "Caffè Nero, 70 Bridge Street, SW1A 2JH, London, Knepp Wildland, United Kingdom",
      "lat": 51.5011196,
      "lon": -0.1246162,
      "name": "Caffè Nero",
      "visibility": {
        "basis": "terrain-and-buildings",
        "buildings": "clear",
        "terrain": "clear",
        "verdict": "clear"
      }
    },
    {
      "angular_offset_deg": 8.5,
      "bearing_deg": 8.5,
      "categories": [
        "cafe"
      ],
      "direction": "directly ahead, about 50 metres",
      "distance_m": 46.9,
      "label": "Treats, 6 Bridge Street, SW1A 2JR, London, Knepp Wildland, United Kingdom",
      "lat": 51.501116800000005,
      "lon": -0.1245003,
      "name": "Treats",
      "visibility": {
        "basis": "terrain-and-buildings",
        "buildings": "clear",
        "terrain": "clear",
        "verdict": "clear"
      }
    }
  ]
}

More from the server's description:

Use this for "what is that over there", "is there a pub in that direction", "what am I looking at". Use nearby_places instead when the question is about what is NEAR a point rather than what is in a direction. Provide lat, lon, bearing_deg (degrees CLOCKWISE FROM TRUE NORTH: 0 north, 90 east, 180 south, 270 west) and at least one of category or name; category=building reaches named buildings. Optional fov_deg is the FULL width of the cone (default 60, so 30 degrees either side), radius_m the range (default 1000, max 5000), eye_height_m the observer's eye height (default 1.6). Each result carries distance_m, bearing_deg, a signed angular_offset_deg (negative left, positive right), a spoken direction, and a visibility block. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).

list_place_categories

List the canonical place categories you can pass as category to nearby_places and search_along_route (and browse on with geocode).

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • Read this before guessing a category: a token that is not on this list matches nothing, and quietly returns an empty result rather than an error.
  • Cuisines, brands and names are NOT categories, search those as free text.

Inputs

No inputs: call it with {}.

Output

FieldTypeAlways presentDescription
categoriesPlaceCategory[]yesEvery category nearby_places, search_along_route and geocode recognise, sorted alphabetically by category.

PlaceCategory: One canonical place category, the phrases that name it, and what it covers.

FieldTypeAlways presentDescription
aliasesstring[]yesColloquial phrases that resolve to this category, sorted. Passing one of these as free text works too, but the canonical category is exact.
categorystringyesThe canonical token to pass as a category filter, e.g. fuel.
descriptionstringyesWhat the category covers, in one line.

Example (called 2026-11-05)

json
{}

Result (trimmed):

json
{
  "categories": [
    {
      "aliases": [
        "off licence"
      ],
      "category": "alcohol",
      "description": "Off-licence: a shop selling alcohol to take away."
    },
    {
      "aliases": [
        "atm",
        "cash machine",
        "… 2 more"
      ],
      "category": "atm",
      "description": "Cash machine, whether free-standing or in a wall."
    },
    "… 30 more"
  ]
}

More from the server's description:

Each entry is a category token (the exact value to send, e.g. "fuel", "charging_station", "hgv_parking"), the aliases that colloquially name it ("petrol station", "EV charger", "lorry park"), and a one-line description of what it covers. Sorted by category and identical on every call. Local lookup, no network, no quota.

Transit

England bus and coach stops and their timetabled departures.

nearby_stops

England bus and coach stops near a point, nearest first. Pass lat and lon (WGS84), optional radius_m (1 to 2,000, default 500) and limit (1 to 50, default 10).

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Bus and coach only: rail, tram, metro and ferry stops are not included, and coverage is England (plus stops just across a border that an England service calls at).
  • Display data, never a journey plan: pass on the returned label and coverage.
  • Display the returned attribution (NaPTAN and BODS, Open Government Licence v3.0).

Inputs

InputTypeRequiredDefaultLimitsDescription
latnumberyesLatitude of the point, WGS84 degrees.
lonnumberyesLongitude of the point, WGS84 degrees.
limitintegerno10Most stops to return, 1 to 50 (default 10), nearest first.
radius_mintegerno500Search radius in metres, 1 to 2,000 (default 500).

Output

FieldTypeAlways presentDescription
attributionstringyesNaPTAN and BODS attribution (Open Government Licence v3.0): display it with the stops.
coveragestringyesWhat the data covers (England bus and coach stops; no rail, tram or ferry). Pass it on with the stops.
labelstringyesWhat the answer is and is not: display data, never a journey plan or an arrival prediction. Pass it on with the stops.
stopsTransitStopOut[]yesStops inside the radius, nearest first.

Example (called 2026-11-05)

json
{
  "lat": 51.5308,
  "lon": -0.1238,
  "limit": 3
}

Result (trimmed):

json
{
  "attribution": "Contains public sector information licensed under the Open Government Licence v3…",
  "coverage": "England bus stops (NaPTAN) with services from BODS timetables. Rail, tram and fe…",
  "label": "Bus and coach stops for display, with the lines the Bus Open Data timetables hav…",
  "stops": [
    {
      "bearing_deg": 137,
      "distance_m": 53,
      "id": "490000129D",
      "indicator": "Stop D",
      "lat": 51.53045,
      "lines": [
        "46",
        "63",
        "… 1 more"
      ],
      "locality": "Kings Cross",
      "lon": -0.12328,
      "name": "King's Cross St. Pancras Underground Station"
    },
    {
      "bearing_deg": 123,
      "distance_m": 61,
      "id": "490000129E",
      "indicator": "Stop E",
      "lat": 51.5305,
      "lines": [
        "30",
        "73",
        "… 4 more"
      ],
      "locality": "Kings Cross",
      "lon": -0.12307,
      "name": "King's Cross St. Pancras Underground Station"
    },
    "… 1 more"
  ]
}

More from the server's description:

Each stop carries its NaPTAN ATCO code id (pass it to next_departures), name, indicator (e.g. "Stop S"), locality, position, distance_m and bearing_deg from the point, and lines, the line names the Bus Open Data timetables have calling there. Requires the MapMap gateway; a deployment without the stops index answers a clear error.

next_departures

Timetabled bus departures at one stop, from the Bus Open Data (BODS) timetables. Pass stop (the NaPTAN ATCO code nearby_stops returns), optional at (RFC 3339, default now; the index holds three service days from today), window_minutes (1 to 180, default 60) and limit (1 to 30, default 8).

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • These are TIMETABLE times: basis is always "timetable", and a live entry is an observation, never an arrival time or a delay.
  • MapMap does not predict arrivals: never present scheduled_at or live as one, and pass on the returned label and coverage verbatim.
  • Display the returned attribution (NaPTAN and BODS, Open Government Licence v3.0).

Inputs

InputTypeRequiredDefaultLimitsDescription
stopstringyesThe stop's NaPTAN ATCO code, as nearby_stops returns it.
atstringnonowStart of the window, RFC 3339 (e.g. "2026-10-12T08:00:00+01:00"); default now. The index holds three service days from today.
limitintegerno8Most departures to return, 1 to 30 (default 8).
window_minutesintegerno60Window length, 1 to 180 minutes (default 60).

Output

FieldTypeAlways presentDescription
attributionstringyesNaPTAN and BODS attribution (Open Government Licence v3.0).
basisstringyesAlways "timetable".
coveragestringyesWhat the data covers. Pass it on with the departures.
departuresTransitDepartureOut[]yesDepartures in timetable order.
labelstringyesThe honesty line, verbatim: timetable times, what a live entry is, and that MapMap does not predict arrival times. Pass it on with the departures.
stopTransitStopOutyesThe stop.
generated_atstringnoWhen the answer was assembled, RFC 3339.
timetable_datestringnoThe service date the requested time falls on, YYYY-MM-DD.

TransitDepartureOut: One timetabled departure.

FieldTypeAlways presentDescription
linestringyesThe line name, e.g. "15".
scheduled_atstringyesThe timetabled departure, RFC 3339 in local time. A timetable time, never a prediction.
tripstringyesThe GTFS trip id.
destinationstringnoThe headsign the timetable gives.
liveTransitLiveOutnoA vehicle on this line seen heading towards the stop, or absent. An observation, never an arrival time.
operatorstringnoNational Operator Code, e.g. "TFLO".

TransitLiveOut: A vehicle seen on the departure's line heading towards the stop.

FieldTypeAlways presentDescription
distance_mintegeryesStraight-line distance from the vehicle to the stop, metres.
vehiclestringyesThe operator's vehicle reference.
age_sintegernoAge of the vehicle's position fix, seconds.

Example (called 2026-11-05)

json
{
  "stop": "490000129D",
  "limit": 3
}

Result (trimmed):

json
{
  "attribution": "Contains public sector information licensed under the Open Government Licence v3…",
  "basis": "timetable",
  "coverage": "England bus stops (NaPTAN) with services from BODS timetables. Rail, tram and fe…",
  "departures": [
    {
      "line": "63",
      "live": {
        "age_s": 110,
        "distance_m": 1175,
        "vehicle": "LC71KWL"
      },
      "operator": "ABLO",
      "scheduled_at": "2026-11-05T08:58:00+00:00",
      "trip": "VJd0e963a207198700b6db8f45221520412ad82117"
    },
    {
      "destination": "St Bartholomew's Hospital",
      "line": "46",
      "live": {
        "age_s": 157,
        "distance_m": 930,
        "vehicle": "LJ18FHG"
      },
      "operator": "MTLN",
      "scheduled_at": "2026-11-05T09:01:00+00:00",
      "trip": "VJ38a64df11ea4ab8cac5af189bf6a1f3df95f6958"
    },
    "… 1 more"
  ],
  "generated_at": "2026-11-05T08:57:13Z",
  "label": "Timetable times from Bus Open Data timetables. A live entry shows a vehicle on t…",
  "stop": {
    "id": "490000129D",
    "indicator": "Stop D",
    "lat": 51.53045,
    "lines": [
      "46",
      "63",
      "… 1 more"
    ],
    "locality": "Kings Cross",
    "lon": -0.12328,
    "name": "King's Cross St. Pancras Underground Station"
  },
  "timetable_date": "2026-11-05"
}

More from the server's description:

Each departure carries line, destination, operator, scheduled_at (local time) and trip, plus live when a vehicle on that line was seen in the live positions within 3 km and heading towards the stop (vehicle, distance_m, age_s). Requires the MapMap gateway; a deployment without the departures index answers a clear error.

Weather and events

Forecast weather along a route and for drawing, reported natural events, space weather and camera-verified road incidents.

weather_along_route

Forecast weather along a route, each sample read for the hour you actually reach it rather than the weather right now.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Each sample carries its ETA, temperature, rain, snowfall, precipitation type and probability, wind and gust speed, visibility, the WMO weather code and severe (thunderstorm and heavy precipitation codes only).
  • It is a FORECAST, not an observation: read on a 0.05 degree grid (about 5.5 km) at the nearest hour, up to 16 days ahead, and samples past that horizon or without data come back available: false with a reason, never painted dry.
  • summary is one sentence (where rain or snow starts and for how far, the strongest gust, samples without data) and always ends by saying it is advisory, never a routing guarantee: keep that qualifier when you pass it on.
  • Display the returned attribution with the forecast, linked to attribution_url, with licence_url (CC BY 4.0) linked; derived_fields names the sample fields MapMap derives rather than Open-Meteo supplies.

Inputs

InputTypeRequiredDefaultLimitsDescription
shapestringyesThe route shape as an encoded polyline6 (the route tool's geometry_polyline6).
depart_atstringnonowDeparture time, RFC 3339 (e.g. "2026-10-12T14:00:00Z"); defaults to now. Forecasts reach about 16 days ahead; samples past that answer unavailable.
duration_snumbernoThe route's duration in seconds, used to work out when you reach each sample. Pass the route tool's duration; omitted, a generic 80 km/h average is assumed and duration_source says so.
sample_interval_mnumberno25Distance between samples in metres (default 25,000, clamped to 5,000 to 100,000; at most 40 samples per route).

Output

FieldTypeAlways presentDescription
attributionstringyesData attribution (CC BY 4.0): display it with the forecast, verbatim.
attribution_urlstringyesThe link the credit carries ("https://open-meteo.com/"): Open-Meteo's licence asks for it next to any place its data is displayed.
depart_atstringyesThe departure time actually used, RFC 3339 UTC.
derived_fieldsstring[]yesThe sample fields MapMap derives rather than reads from Open-Meteo ("precipitation_type", "severe"), so a display can indicate the changes the licence asks to be indicated.
duration_snumberyesThe route duration actually used, seconds.
duration_sourcestringyes"provided" or "estimated_default_speed" (an 80 km/h guess: treat the ETAs as approximate).
licence_urlstringyesThe data licence to link with the credit (CC BY 4.0).
sample_interval_mnumberyesThe sample spacing actually used, metres (may be coarser than asked on a long route).
samplesWeatherSampleOut[]yesRoute samples, start to end.
severe_aheadbooleanyesTrue when any available sample carries a severe WMO code.
summarystringyesOne honest sentence describing the samples: where rain or snow starts and for how far, the strongest gust, how many samples had no data. Always ends by saying it is an advisory forecast, never a routing guarantee.

WeatherSampleOut: One route sample's forecast, read for the hour you reach it.

FieldTypeAlways presentDescription
arc_mnumberyesDistance along the route to this sample, metres.
availablebooleanyesWhether real forecast data is present. false means every weather field is absent and unavailable_reason says why; never a guess.
etastringyesEstimated time of arrival at this sample, RFC 3339 UTC.
latnumberyesSample latitude.
lonnumberyesSample longitude.
forecast_hourstringnoThe forecast hour actually served (UTC, YYYY-MM-DDTHH:00).
precipitation_mmnumbernoTotal precipitation for the hour, mm.
precipitation_probability_pctnumbernoChance of precipitation, percent.
precipitation_typestringno"rain", "snow", "mixed" or "none".
rain_mmnumbernoRain for the hour, mm.
severebooleannoTrue for thunderstorm and heavy-precipitation WMO codes only. Absent when the sample is unavailable.
snowfall_cmnumbernoSnowfall for the hour, cm.
temperature_cnumbernoAir temperature, degrees C.
unavailable_reasonstringnoWhy this sample has no data (beyond the forecast horizon, or the forecast source could not be reached).
visibility_mnumbernoVisibility, metres.
weather_codenumbernoThe raw WMO weather code.
wind_gusts_kphnumbernoGust speed, km/h: the figure that matters for high-sided vehicles.
wind_speed_kphnumbernoSustained wind speed, km/h.

Example (called 2026-11-05)

json
{
  "shape": "gejfaB~zrFDCRGXC^?rCXvD`…",
  "duration_s": 917.918
}

Result (trimmed):

json
{
  "attribution": "Weather data by Open-Meteo.com (CC BY 4.0)",
  "attribution_url": "https://open-meteo.com/",
  "depart_at": "2026-11-05T08:57:12.611948075Z",
  "derived_fields": [
    "precipitation_type",
    "severe"
  ],
  "duration_s": 917.918,
  "duration_source": "provided",
  "licence_url": "https://creativecommons.org/licenses/by/4.0/",
  "sample_interval_m": 5098.2,
  "samples": [
    {
      "arc_m": 0,
      "available": true,
      "eta": "2026-11-05T08:57:12.611948075Z",
      "forecast_hour": "2026-11-05T09:00",
      "lat": 51.500644,
      "lon": -0.124864,
      "precipitation_mm": 0,
      "precipitation_probability_pct": 2,
      "precipitation_type": "none",
      "rain_mm": 0,
      "severe": false,
      "snowfall_cm": 0,
      "temperature_c": 9.9,
      "visibility_m": 22240,
      "…": "3 more fields"
    },
    {
      "arc_m": 5098.2,
      "available": true,
      "eta": "2026-11-05T09:12:30.529948075Z",
      "forecast_hour": "2026-11-05T09:00",
      "lat": 51.530695,
      "lon": -0.124488,
      "precipitation_mm": 0,
      "precipitation_probability_pct": 2,
      "precipitation_type": "none",
      "rain_mm": 0,
      "severe": false,
      "snowfall_cm": 0,
      "temperature_c": 9.6,
      "visibility_m": 19140,
      "…": "3 more fields"
    }
  ],
  "severe_ahead": false,
  "summary": "No rain, snow or strong gusts forecast at the sampled points. This is a forecast…"
}

More from the server's description:

Pass the route's shape (an encoded polyline6, the route tool's geometry_polyline6), optional depart_at (RFC 3339, default now), the route's duration_s (from route; omitted, an 80 km/h average is assumed and duration_source says so) and optional sample_interval_m (default 25,000, 5,000 to 100,000, at most 40 samples). Requires the MapMap gateway; a deployment without a weather source answers a clear error.

weather_frames

The NOAA GFS forecast frames MapMap publishes for drawing weather on a map: 17 frames (0 to 48 hours every 3 hours) of the newest run, each with URLs for the global rain, temperature, cloud, gust and sea-level pressure PNG fields (1440 by 721, value-encoded; decode with encodings), the wind field (PNG plus JSON ranges) and XYZ tile templates for rain, temperature and isobars.

Access: Keyless. Answers without a key on the hosted endpoint; the frame URLs it returns need your own MapMap key to fetch.

Caveats the server states:

  • A FORECAST, not an observation, processed by MapMap from NOAA GFS 0.25 degree data (regridded and quantised): never describe it as original NOAA data or imply NOAA endorsement.
  • Display the returned attribution.

Inputs

InputTypeRequiredDefaultLimitsDescription
fieldsstring[]noWhich fields to return URLs for, any of rain, temp, cloud, gust, mslp, wind, isobars. Default: all.
runstringnoA forecast run as YYYYMMDDHH (UTC), e.g. "2026101700". Default: the newest run.

Output

FieldTypeAlways presentDescription
attributionstringyesNOAA GFS credit. Display it.
encodingsany JSONyesHow to decode each PNG field (unit, scale, offset), verbatim from the manifest.
framesWeatherFrameOut[]yesThe frames, in hour order.
generated_atstringyesWhen MapMap published the manifest (RFC 3339 UTC).
labelstringyes"Forecast, not an observation". Pass it on.
modelWeatherModelOutyesThe forecast model.
notestringyesDerived from NOAA GFS and modified by MapMap; no NOAA endorsement is implied. Pass it on.
processed_bystringyes"MapMap": the data is processed by MapMap.
runstringyesThe run time (RFC 3339 UTC).
run_idstringyesThe run as YYYYMMDDHH.
statusstringyesok, stale (the newest run is older than the threshold) or unknown (no frames). Pass a stale or unknown status on.
summarystringyesOne sentence for the answer. Pass it on with its qualifier.
threshold_sintegeryesThe age in seconds after which the frames count as stale.
age_sintegernoSeconds since generated_at when the answer was assembled.

WeatherFrameOut: One forecast frame.

FieldTypeAlways presentDescription
hourintegeryesHours after the run time.
tilesmap of stringyesXYZ tile templates by field (rain, temp: PNG; isobars: MVT).
urlsmap of stringyesAbsolute URLs by field (global value-encoded PNGs; wind also has a JSON ranges file under wind_json). Each needs the caller's own MapMap key.
valid_atstringyesThe time the frame is valid for (RFC 3339 UTC).
halfmap of stringnoHalf-resolution PNG URLs by field, when the run has them.

WeatherModelOut: The forecast model behind the frames.

FieldTypeAlways presentDescription
idstringyesModel id ("gfs").
frames_per_runintegernoFrames in a run.
horizon_hintegernoHours covered from the run time.
resolution_degnumbernoGrid resolution of the source data, degrees.
step_hintegernoHours between frames.

Example (called 2026-11-05)

json
{
  "fields": [
    "rain"
  ]
}

Result (trimmed):

json
{
  "age_s": 15957,
  "attribution": "Weather: NOAA GFS forecast (public domain), processed by MapMap",
  "encodings": {
    "cloud": {
      "formula": "(R*65536 + G*256 + B) * scale + offset",
      "maplibre": {
        "baseShift": 0,
        "blueFactor": 0.01,
        "encoding": "custom",
        "greenFactor": 2.56,
        "redFactor": 655.36
      },
      "offset": 0,
      "scale": 0.01,
      "unit": "%"
    },
    "gust": {
      "formula": "(R*65536 + G*256 + B) * scale + offset",
      "maplibre": {
        "baseShift": 0,
        "blueFactor": 0.01,
        "encoding": "custom",
        "greenFactor": 2.56,
        "redFactor": 655.36
      },
      "offset": 0,
      "scale": 0.01,
      "unit": "m/s"
    },
    "mslp": {
      "formula": "(R*65536 + G*256 + B) * scale + offset",
      "maplibre": {
        "baseShift": 0,
        "blueFactor": 0.01,
        "encoding": "custom",
        "greenFactor": 2.56,
        "redFactor": 655.36
      },
      "offset": 0,
      "scale": 0.01,
      "unit": "hPa"
    },
    "rain": {
      "formula": "(R*65536 + G*256 + B) * scale + offset",
      "maplibre": {
        "baseShift": 0,
        "blueFactor": 0.01,
        "encoding": "custom",
        "greenFactor": 2.56,
        "redFactor": 655.36
      },
      "offset": 0,
      "scale": 0.01,
      "unit": "mm/h"
    },
    "temp": {
      "formula": "(R*65536 + G*256 + B) * scale + offset",
      "maplibre": {
        "baseShift": 100,
        "blueFactor": 0.01,
        "encoding": "custom",
        "greenFactor": 2.56,
        "redFactor": 655.36
      },
      "offset": -100,
      "scale": 0.01,
      "unit": "degC"
    }
  },
  "frames": [
    {
      "half": {
        "rain": "https://api.mapmap.ai/v1/weather/frames/gfs/2026110500/rain/f000.half.png"
      },
      "hour": 0,
      "tiles": {
        "rain": "https://api.mapmap.ai/tiles/weather-gfs-2026110500-rain-f000/{z}/{x}/{y}.png"
      },
      "urls": {
        "rain": "https://api.mapmap.ai/v1/weather/frames/gfs/2026110500/rain/f000.png"
      },
      "valid_at": "2026-11-05T00:00:00Z"
    },
    {
      "half": {
        "rain": "https://api.mapmap.ai/v1/weather/frames/gfs/2026110500/rain/f003.half.png"
      },
      "hour": 3,
      "tiles": {
        "rain": "https://api.mapmap.ai/tiles/weather-gfs-2026110500-rain-f003/{z}/{x}/{y}.png"
      },
      "urls": {
        "rain": "https://api.mapmap.ai/v1/weather/frames/gfs/2026110500/rain/f003.png"
      },
      "valid_at": "2026-11-05T03:00:00Z"
    },
    "… 15 more"
  ],
  "generated_at": "2026-11-05T04:31:16Z",
  "label": "Forecast, not an observation",
  "model": {
    "frames_per_run": 17,
    "horizon_h": 48,
    "id": "gfs",
    "resolution_deg": 0.25,
    "step_h": 3
  },
  "note": "Derived from NOAA GFS data and modified by MapMap (regridded, quantised and thin…",
  "processed_by": "MapMap",
  "run": "2026-11-05T00:00:00Z",
  "run_id": "2026110500",
  "status": "ok",
  "summary": "GFS run 2026-11-05 00:00 UTC: 17 frames, 0 to 48 h every 3 h. A forecast, not an…",
  "threshold_s": 25200
}

More from the server's description:

Optional run (YYYYMMDDHH) and fields. The URLs need the caller's own MapMap key. status says whether the newest run is fresh. Requires the MapMap gateway.

events_in_area

Earthquakes, volcano alerts and satellite fire detections in a bounding box, for display. Pass bbox [west, south, east, north] (WGS84), optional types (quake, volcano, fire; default all), min_magnitude (quakes, default 2.5) and limit (1 to 500, default 100).

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Earthquakes are USGS, magnitude 2.5 and above, past 24 hours, as reported (magnitudes and locations may be revised).
  • Volcano alerts are USGS-monitored volcanoes ONLY: an absent alert says nothing about volcanoes elsewhere.
  • Fire cells are NASA FIRMS VIIRS detections thinned to 0.5 degree cells: satellite-detected heat, NOT confirmed fires.
  • Each feed carries status (ok, stale, unknown): pass a stale or unknown status on, never present old data as current.
  • This is reported data for display, not a warning service and not safety-critical; keep that qualifier when you pass it on.
  • Display the returned attribution, and for fire data the FIRMS acknowledgment and disclaimer.

Inputs

InputTypeRequiredDefaultLimitsDescription
bboxnumber[]yesexactly 4 itemsBounding box [west, south, east, north], WGS84 degrees. West must be less than east and south less than north; a box crossing the antimeridian is not supported (query two boxes).
limitintegerno100Most events to return, 1 to 500 (default 100). Newest quakes first, then volcano alerts, then the busiest fire cells.
min_magnitudenumberno2.5Smallest earthquake magnitude, 2.5 to 9.0 (default 2.5). Quakes only.
typesstring[]noWhich feeds to include: any of quake, volcano, fire. Default: all three.

Output

FieldTypeAlways presentDescription
acknowledgmentstringyesNASA FIRMS acknowledgment. Display it with fire data.
attributionmap of stringyesSource credits, keyed by feed. Display them.
countintegeryesEvents returned.
coveragestringyesWhat each feed does and does not cover. Pass it on.
disclaimerstringyesNASA FIRMS "as is" disclaimer. Display it with fire data.
endorsementstringyesNo endorsement by or affiliation with USGS, NOAA or NASA is implied.
eventsEventOut[]yesThe events: newest quakes, then volcano alerts, then fire cells.
feedsmap of EventsFeedOutyesFreshness of each requested feed, keyed quake, volcano, fire.
labelstringyesReported events for display; not a warning service. Pass it on.
summarystringyesOne sentence built from the counts. Pass it on with its qualifier.
truncatedbooleanyesTrue when the limit cut the list short.

EventOut: One earthquake, volcano alert or fire cell.

FieldTypeAlways presentDescription
idstringyesThe event's id (a fire cell id is fire:<south>,<west>).
kindstringyesquake, volcano or fire.
latnumberyesLatitude, WGS84 degrees. A fire cell is its 0.5 degree cell centre.
lonnumberyesLongitude, WGS84 degrees.
acq_datestringnoFire: the acquisition date (YYYY-MM-DD).
alert_levelstringnoVolcano: USGS alert level (NORMAL, ADVISORY, WATCH, WARNING).
cell_degnumbernoFire: the cell size in degrees (0.5).
color_codestringnoVolcano: USGS aviation colour code as a word (GREEN, YELLOW, ORANGE, RED).
countintegernoFire: satellite detections inside the cell.
coveragestringnoVolcano: US-monitored volcanoes only; an absent alert says nothing about volcanoes elsewhere.
depth_kmnumbernoQuake: depth in kilometres.
derivedstringnoFire: says the cell is derived from detections, not a fire.
high_confidence_sharenumbernoFire: share of detections the satellite flags high confidence, 0 to 1.
labelstringnoFire: "Satellite-detected heat, not confirmed fires". Keep it.
magnumbernoQuake: magnitude as reported (may be revised).
max_frpnumbernoFire: the largest fire radiative power in the cell, megawatts.
namestringnoVolcano: name.
notice_urlstringnoVolcano: the USGS notice page.
observatorystringnoVolcano: the monitoring observatory.
placestringnoQuake: USGS place description, e.g. "12 km SW of Ridgecrest, CA".
sent_atstringnoVolcano: when the notice was sent, RFC 3339 UTC.
timestringnoQuake: origin time, RFC 3339 UTC.
urlstringnoQuake: USGS event page.

EventsFeedOut: One feed's freshness, as the gateway reports it.

FieldTypeAlways presentDescription
statusstringyesok (fetched within the threshold), stale (older; features are still returned, so say they are paused) or unknown (no data for this feed). Never present stale or unknown data as current.
age_sintegernoSeconds since fetched_at when the gateway assembled the answer.
count_in_fileintegernoEvents in the feed's file before the box and filters were applied.
detectionsintegernoFires only: raw satellite detections behind the thinned cells.
fetched_atstringnoWhen the box last fetched the feed (RFC 3339), if known.
sourcestringnoThe feed's source, in words.
threshold_sintegernoThe age in seconds after which the feed counts as stale.

Example (called 2026-11-05)

json
{
  "bbox": [
    -10,
    49,
    2,
    59
  ]
}

Result (trimmed):

json
{
  "acknowledgment": "We acknowledge the use of data and/or imagery from NASA's Fire Information for R…",
  "attribution": {
    "fire": "Fire detections: NASA FIRMS, VIIRS (Suomi NPP). Satellite-detected heat, not con…",
    "quake": "Earthquakes: USGS Earthquake Hazards Program (US public domain). Shown as report…",
    "volcano": "Volcano alerts: USGS Hazard Notification System, with locations from the NOAA NC…"
  },
  "count": 0,
  "coverage": "Earthquakes: worldwide, USGS catalogue. Volcano alerts: US-monitored volcanoes o…",
  "disclaimer": "The information presented through LANCE, GIBS, Worldview, and FIRMS are provided…",
  "endorsement": "No endorsement by or affiliation with USGS, NOAA or NASA is implied.",
  "events": [],
  "feeds": {
    "fire": {
      "age_s": 11497,
      "count_in_file": 0,
      "detections": 0,
      "fetched_at": "2026-11-05T05:45:36Z",
      "source": "NASA FIRMS VIIRS (Suomi NPP) detections, past 24 hours, thinned to 0.5 degree cells",
      "status": "ok",
      "threshold_s": 46800
    },
    "quake": {
      "age_s": 34,
      "count_in_file": 28,
      "fetched_at": "2026-11-05T08:56:39Z",
      "source": "USGS Earthquake Hazards Program real-time feed, magnitude 2.5 and above, past 24 hours",
      "status": "ok",
      "threshold_s": 1800
    },
    "volcano": {
      "age_s": 2259,
      "count_in_file": 3,
      "fetched_at": "2026-11-05T08:19:34Z",
      "source": "USGS Hazard Notification System elevated alerts, placed with NOAA NCEI locations",
      "status": "ok",
      "threshold_s": 10800
    }
  },
  "label": "Reported events for display. Not a warning service and not safety-critical.",
  "summary": "No earthquakes, no volcano alerts, no fire cells in this area. Reported for disp…",
  "truncated": false
}

More from the server's description:

Requires the MapMap gateway.

space_weather_now

The planetary Kp index and the NOAA SWPC OVATION aurora forecast right now. No input.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Returns kp (0 to 9) with its time, the forecast's valid time, the peak aurora probability and aurora_png_url (a 360 by 181 probability grid, percent per pixel, column 0 at 180 W, row 0 at 90 N) when the forecast is under an hour old; status is unavailable when it is older and nothing should be drawn.
  • A FORECAST about 30 to 90 minutes ahead, not an observation; never present it as a sighting or a guarantee.
  • Display the returned attribution; no endorsement by NOAA is implied.

Inputs

No inputs: call it with {}.

Output

FieldTypeAlways presentDescription
attributionstringyesNOAA SWPC credit. Display it.
endorsementstringyesNo endorsement by or affiliation with NOAA is implied.
labelstringyes"Forecast, not an observation". Pass it on.
statusstringyesok, unavailable (the forecast is over an hour old: draw nothing) or unknown (no data).
summarystringyesOne sentence for the answer. Pass it on with its qualifier.
threshold_sintegeryesThe age in seconds after which the forecast is unavailable.
age_sintegernoSeconds since the forecast time when the answer was assembled.
aurora_png_urlstringnoURL of the 360 by 181 probability grid (percent per pixel, column 0 at 180 W, row 0 at 90 N); present only while the forecast is fresh. Needs the caller's own MapMap key.
forecast_timestringnoThe time the aurora forecast is valid for (RFC 3339 UTC).
kpnumbernoPlanetary Kp index, 0 to 9.
kp_timestringnoWhen the Kp value applies (RFC 3339 UTC).
max_probabilityintegernoPeak aurora probability anywhere on the grid, percent.
observation_timestringnoThe observation the forecast was made from (RFC 3339 UTC).

Example (called 2026-11-05)

json
{}

Result (trimmed):

json
{
  "age_s": 0,
  "attribution": "Aurora and Kp: NOAA Space Weather Prediction Center (public domain). OVATION aur…",
  "aurora_png_url": "https://api.mapmap.ai/v1/space-weather/aurora.png",
  "endorsement": "No endorsement by or affiliation with NOAA is implied.",
  "forecast_time": "2026-11-05T09:58:00Z",
  "kp": 1,
  "kp_time": "2026-11-05T08:51:00",
  "label": "Forecast, not an observation",
  "observation_time": "2026-11-05T08:47:00Z",
  "status": "ok",
  "summary": "Kp 1.0; NOAA SWPC aurora forecast for 09:58 UTC. A forecast about 30 to 90 minut…",
  "threshold_s": 3600
}

More from the server's description:

Requires the MapMap gateway.

camera_incidents

Road incidents a TRAFFIC CAMERA SAW and a vision model CONFIRMED, in force now: near a point (near [lat, lon] with radius_m, default 1000, at most 10000), in a bbox [min_lon, min_lat, max_lon, max_lat], or along a route shape (polyline6 from route, corridor buffer_m default 60).

Access: Not enabled on the hosted endpoint. Registered, but the hosted gateway has not armed camera-verified incidents, so it answers HTTP 501 there today.

Caveats the server states:

  • Camera records RANK BELOW official closures and never retract one.
  • READ coverage_note ALOUD: an empty list means no camera-verified incident here, NEVER a clear road.
  • Show attribution verbatim (Powered by TfL Open Data).
  • Off unless the deployment arms camera incidents (SN_CAMERA_INCIDENTS_ENABLED): an unarmed gateway answers 501 and says so.

Inputs

InputTypeRequiredDefaultLimitsDescription
bboxnumber[]noexactly 4 items[min_lon, min_lat, max_lon, max_lat].
buffer_mnumbernoCorridor half-width for shape, metres.
include_recently_expiredbooleannofalseInclude records that have expired in the last hour, for the operator's timeline (default false).
min_confidencenumberno0Drop records below this confidence (default 0).
nearnumber[]no1000exactly 2 items[lat, lon] plus radius_m (default 1000).
radius_mnumbernoRadius for near, metres, at most 10000.
shapestringno60A route shape as polyline6; the corridor rule of /v1/incidents/along applies (buffer_m default 60).

Output

FieldTypeAlways presentDescription
attributionstring[]yesAttribution lines required by the camera providers, verbatim.
coverage_notestringyesPlain-language statement of which cameras are covered and that camera records rank below official closures. Agents must read it aloud: an empty list means "no camera-verified incident", never "road clear".
incidentsCameraIncidentOut[]yesRecords in force (and recently expired, if asked).

CameraIncidentOut: One camera-verified incident in force.

FieldTypeAlways presentDescription
camera_namestringyesCamera name.
camera_positionnumber[]yesCamera position [lat, lon].
confidencenumberyesVerifier confidence, 0..=1.
endstringyesLifts at, RFC 3339.
idstringyesOverride record id.
incident_kindIncidentKindyesWhat was seen.
kindstringyesclosed or speed_override.
sourcestringyescam-<provider>-<camera id>.
startstringyesIn force from, RFC 3339.
verifierstringyesWhich verifier answered.
waysWayOut[]yesRoads asserted.
arc_mnumbernoDistance along the route, metres, when queried by shape.
evidence_urlstringnoThe clip the decision was made on.
speed_kphintegernoSpeed in force, km/h, for speed_override.

IncidentKind: What kind of road event an incident describes, as far as a closure decision cares.

One of:

  • "collision": Two or more vehicles made contact.
  • "stalled-vehicle": A vehicle stopped where it should not have.
  • "wrong-way": A vehicle travelling against the carriageway direction.
  • an object with other: string: Anything else, carrying the raw category for logs.

WayOut: One road asserted.

FieldTypeAlways presentDescription
directionDirectionyesCarriageway.
way_idintegeryesOSM way id.
namestringnoRoad name, when known.

Example (called 2026-11-05)

json
{
  "near": [
    51.5007,
    -0.1246
  ]
}

The hosted endpoint answered with this tool error:

text
gateway /v1/camera-incidents answered HTTP 501: camera-verified incidents are not enabled on this gateway: the operator must set SN_CAMERA_INCIDENTS_ENABLED=true with SN_INCIDENTS_DIR (reads) or SN_OPS_OVERRIDES_FILE (operator closures)

More from the server's description:

Give exactly one. Each record says what it asserts (closed or a speed_override in km/h) on which OSM ways and carriageway, what was seen (collision, stalled vehicle, wrong-way vehicle), the camera, the clip URL the verdict was made on (evidence_url), the verifier and its confidence, and when it lifts (end, always within minutes: these records expire on their own). Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).

Clearance and ADR

Dangerous-goods tunnel decisions and measured overhead clearance.

check_adr_tunnel

Check whether a vehicle may pass through a tunnel of a given ADR category ("A"–"E"). Provide hazmat and, when known, the load's ADR 8.6.4 tunnel restriction code (e.g.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • Applies the conservative worst-case reading: conditional clauses are assumed to apply, so a blocked answer may over-restrict but never under-restricts.

Inputs

InputTypeRequiredDefaultLimitsDescription
hazmatbooleanyesWhether the vehicle carries dangerous goods at all. When false the ADR tunnel matrix does not apply and every tunnel is permitted.
tunnel_categorystringyesADR category of the tunnel to check: "A", "B", "C", "D" or "E".
tunnel_codestringnoADR 8.6.4 tunnel restriction code of the load, e.g. "B", "C5000D", "B/D", or "(-)"/"none". Leave unset for a hazmat load of unknown code (conservatively treated as code B).

Output

FieldTypeAlways presentDescription
decisionDecisionStatusyesWhether passage is allowed or blocked.
explanationstringyesHuman-readable explanation of how the decision was reached, including the conservative worst-case reading.
forbidden_tunnel_categoriesstring[]yesADR tunnel categories this load is forbidden from under the worst-case reading (conditional clauses assumed to apply). Empty when unrestricted.
reasonstringnoWhy passage is forbidden (present only when blocked); cites the ADR 8.6.4 rule that applied.

DecisionStatus: The tunnel-entry decision, mirroring sn_adr::Decision.

ValueMeaning
"allowed"Passage is permitted.
"blocked"Passage is forbidden.

Example (called 2026-11-05)

json
{
  "hazmat": true,
  "tunnel_category": "E",
  "tunnel_code": "D"
}

Result (trimmed):

json
{
  "decision": "blocked",
  "explanation": "Vehicle with declared tunnel restriction code D: passage through a category-E tu…",
  "forbidden_tunnel_categories": [
    "D",
    "E"
  ],
  "reason": "ADR 8.6.4: tunnel restriction code D forbids passage through tunnels of category…"
}

More from the server's description:

"B", "C5000D", "B/D", "none"). No network access; answers instantly.

check_clearance_on_route

Measure a vehicle's overhead clearance along a route against surveyed point cloud geometry, wherever survey coverage exists.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Honesty, and it matters here: this measures physical geometry from a dated survey.
  • It is not a signed or posted height, clearance_enforcement.route_certified is always false, and the caveat is on every answer including the clear one.
  • Ground the survey did not cover comes back as not_surveyed_m and is never judged, so a pass is possible over complete coverage and nowhere else; sparse or stale coverage comes back separately as insufficient_data_m.
  • Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), which holds the surveys; there is no fallback, and it will not answer from the routing step alone.

Inputs

InputTypeRequiredDefaultLimitsDescription
destinationLatLonyesRoute destination.
height_mnumberyesvehicleVehicle height in metres. Required: there is no default vehicle, because a default vehicle is how somebody gets an answer about a lorry that is not theirs.
originLatLonyesRoute origin.
margin_mnumberno0Operating margin in metres, added to the height before the verdict is decided (default 0). Your compliance policy, not ours: the measured figure and the safe bound are both reported whatever you set here, and the margin is echoed back. It is applied to the measurement, not to the routing step, where the map's own posted heights already carry a margin of their own.
width_mnumbernoVehicle width in metres. Supplying it asks the width axis as well as the height axis; the width answer is reported separately and is never a headroom.

Output

FieldTypeAlways presentDescription
advisoriesstring[]yesNotes on stretches of the route: vegetation age, a reading limited by a wire, a seam between surveys.
assessed_mnumberyesMetres of route measured against survey data good enough to decide on.
basisstringyesAlways "surveyed_geometry_not_signage".
clearance_enforcementClearanceEnforcementyesWhat this answer does and does not prove. Read it.
datasetsClearanceDatasetSummary[]yesThe surveys drawn on, with their capture dates and staleness.
explanationstringyesWhat was measured, when, with what bound, and what the tool declined to conclude. It may over-restrict; it never under-restricts.
geometry_polyline6stringyesThe route as a six-digit-precision encoded polyline, so the same shape can be drawn or re-measured without routing again.
indeterminate_reasonsstring[]yesWhy an indeterminate could not be called, as machine-readable codes (inside_uncertainty_band, vegetation_age_exceeded, artefact_stale, artefact_freshness_unchecked). Empty on every other verdict. These are for branching on, not for reading out: the same reasons appear as English in explanation, and a person shown artefact_freshness_unchecked has been failed by whatever displayed it.
insufficient_data_mnumberyesMetres of route a survey covers but too sparsely, or too stale, to decide on. Also an absence, and reported apart from not_surveyed_m because the two have different remedies.
not_surveyed_mnumberyesMetres of route no survey covers. This is the absence of a measurement, and it is never the same statement as a measured open sky. No verdict is drawn over it.
route_distance_mnumberyesLength of the route the vehicle was routed over, metres.
route_duration_snumberyesEstimated driving time for that route, seconds.
verdictstringyespass, fail, indeterminate or no_verdict for the height axis. A pass is possible over completely surveyed ground and nowhere else: unsurveyed ground yields no_verdict, never a pass.
widthClearanceWidthSummaryyesThe width axis, answered separately when width_m was given.
limitingLimitingPointSummarynoThe limiting point on a fail, the tightest point on a pass, the worst contested point on an indeterminate. Absent on a no_verdict, and absent on a pass where the survey found nothing at all above the corridor.
resolution_hintstringnoWhat would resolve an indeterminate, in one sentence, as the measuring service phrased it.
view_urlstringnoDeep link to that exact view in the survey viewer, so the reading can be looked at rather than taken on trust.

ClearanceEnforcement: How a clearance answer was arrived at, and what it therefore does and does not prove. The TunnelEnforcement pattern, applied to the other safety-adjacent answer this server gives, and for the same reason: a headroom figure with a bound on it reads like permission. It is not one. The block is carried on every response including the clear one, because the clear one is the dangerous one. Every field is taken verbatim from the gateway's report rather than rebuilt here. vertical_datum is a property of the survey the figures came from, so this server cannot know it; and copying the caveat text into a second crate is exactly the drift the single-source-of-truth rule exists to prevent. When the gateway omits any part of it, the tool refuses the answer rather than emitting a report with a hollow caveat.

FieldTypeAlways presentDescription
basisstringyesHow the figures were arrived at. Always "surveyed_pointcloud_within_survey_difference": the difference between two heights measured in the same survey, on the same date, by the same processing. No map tag, no terrain model and no absolute vertical datum enters it.
caveatstringyesWhat this answer does and does not prove, in one paragraph.
route_certifiedbooleanyesWhether the route has been certified against a structure inventory. Always false, and there is no future in which it is true: this is a measurement with a bound, never a certificate about a route.
vertical_datumstringyesThe vertical frame the surveyed figures live in, spelled for a reader.

ClearanceDatasetSummary: One survey the report drew on.

FieldTypeAlways presentDescription
datasetstringyesThe dataset id.
stalebooleanyesWhether the clearance field lags the survey it was baked from, or has never been checked against it. Either way no route passes on it.
survey_datesstringyesCapture range, from/to in ISO dates.

LimitingPointSummary: One measured clearance on the route, with the bounds that qualify it. There is no field here for a posted, signed or otherwise legally binding height, and that absence is deliberate. A survey measures the physical gap; a sign records a restriction a road authority posted, set below the physical gap on purpose. They are different quantities and this server never reports one as the other.

FieldTypeAlways presentDescription
datasetstringyesThe dataset the measurement came from.
headroom_mnumberyesMeasured headroom: the surveyed gap between the road surface and the lowest validated surface above it, metres.
latnumberyesLatitude of the point, decimal degrees.
lonnumberyesLongitude of the point, decimal degrees.
overhead_classstringyesWhat the overhead surface is made of: structure, vegetation, wire or unknown. Foliage is compressible and seasonal, so an old reading of it stops deciding.
route_distance_mnumberyesHow far along the route the point sits, metres.
safe_headroom_mnumberyesheadroom_m less the uncertainty terms, metres. This is the number the verdict is decided on, and the number to plan against.
sampling_gap_mnumberyesOne-sided sampling bias bound, metres: how far below the lowest sample the true low point could hang, given the sample spacing.
sigma_mnumberyesOne-sigma measurement uncertainty on that figure, metres.
surveyed_onstringyesLast capture date of the survey behind this measurement, ISO.

ClearanceWidthSummary: The width axis: how wide the clear corridor is, never how tall. A separate type from LimitingPointSummary on purpose, with no headroom field on it. These are different physical quantities, and a shared type is how the reader of a width answer ends up quoting a headroom.

FieldTypeAlways presentDescription
verdictstringyespass, fail, indeterminate, no_verdict, or not_assessed where the geometry or the request did not support an answer.
clear_width_mnumbernoFree width of the narrowest clear corridor found, metres. Present where a corridor edge was actually measured.
reasonstringnoWhy the width axis declined to answer, present for not_assessed.
safe_clear_width_mnumbernoThat width less its uncertainty terms, metres: the number the width verdict is decided on.

Example (called 2026-11-05)

json
{
  "origin": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "destination": {
    "lat": 51.5308,
    "lon": -0.1238
  },
  "height_m": 4
}

Result (trimmed):

json
{
  "advisories": [],
  "assessed_m": 0,
  "basis": "surveyed_geometry_not_signage",
  "clearance_enforcement": {
    "basis": "surveyed_pointcloud_within_survey_difference",
    "caveat": "Headroom figures are measured from a dated survey's own geometry as a within-sur…",
    "route_certified": false,
    "vertical_datum": "No survey covered this corridor, so no vertical frame applies."
  },
  "datasets": [],
  "explanation": "Routed with truck costing at 4.00 m, so the search avoided the height restrictio…",
  "geometry_polyline6": "gejfaB~zrFDCRGXC^?rCXvD`@|@H`@J`@\\\\b@P^Pf@Nd@Px@Jp@HfA@fAC`Aq@fKeA|OK`ASlAY|@_@r…",
  "indeterminate_reasons": [],
  "insufficient_data_m": 0,
  "not_surveyed_m": 5098.180376212733,
  "route_distance_m": 5106,
  "route_duration_s": 917.918,
  "verdict": "no_verdict",
  "width": {
    "reason": "no vehicle width was supplied, so the corridor was assessed for height alone",
    "verdict": "not_assessed"
  }
}

More from the server's description:

Routes with truck costing (so the search already avoids the height restrictions the map has tagged), then measures that corridor. Give origin, destination and height_m; optional width_m asks the corridor-width axis too, and optional margin_m adds your operating margin to the vehicle before the verdict. Returns pass, fail, indeterminate or no_verdict with the limiting point, the measured headroom, its uncertainty bound (safe_headroom_m, sigma_m, sampling_gap_m) and a link to that exact view in the survey viewer. An indeterminate carries indeterminate_reasons as codes to branch on and the same reasons as English inside explanation; read out the English. A pass may carry no limiting point at all, which means the survey found nothing above that corridor, and the width axis may answer not_assessed where the corridor edges are too sparsely surveyed while the height axis still answers. Reach for this when a truck route came back unchanged and you need to know whether that means anything: an unchanged route avoids what the map records, which is a different claim from measured headroom, because a structure nobody tagged is routed through like open road. The mapmap://guide/clearance resource sets out what each answer proves.

Verify

Check that places an AI named are real, and that a dataset's declared CRS fits its coordinates.

verify_places

Check whether places (and itineraries) an AI mentioned are real, findable and physically possible.

Access: Keyless. Answers without a key on the hosted endpoint under fair use; present your own key to meter it to you.

Caveats the server states:

  • Pass structured claims (reliable, and the only path that supports itinerary feasibility) or free text (best-effort quoted-phrase extraction).
  • Each claim resolves to exactly one of three verdicts, never a boolean: "verified" (matched a real place, with its stable id and the source/date of the evidence), "contradicted" (a specific, dated, sourced fact rules it out, currently only an itinerary leg the routing engine proves cannot be driven in the stated time, with the computed travel time as evidence), or "unverified" (no evidence either way).
  • This tool NEVER asserts that a named real business does not exist or has closed, that would be a defamation risk with no upside; a missing match is always "unverified".
  • A claim takes only id, name, locality, sequence and claimed_time; any other field (e.g.
  • near) is refused naming the accepted ones, so put disambiguating context such as a town in locality.

Inputs

InputTypeRequiredDefaultLimitsDescription
claimsVerifyClaimInput[]noStructured claims, the reliable path, and the only path that supports itinerary feasibility. Mutually exclusive with text. Max 20 per request.
costingCostingKindno"auto"Costing for feasibility legs: "auto" (default), "truck", "bicycle", "pedestrian" or "motor_scooter".
textstringnoFree text to extract place claims from (best-effort heuristic: quoted phrases and Title Case runs after "at/in/near/to/from/ visiting", not NLP or an LLM call, and it does not attempt itinerary feasibility since there are no explicit times to anchor legs to). Mutually exclusive with claims. Max 8,000 characters.

VerifyClaimInput: One place claim for the verify_places tool: a place an AI mentioned, to be checked for existence and (as part of an itinerary) feasibility. Unknown fields are refused, naming the accepted ones. A dev-lane run (19 Oct 2026) passed near instead of locality; it was silently dropped, "Roman Baths" matched Ankara, and the itinerary leg became uncomputable with nothing telling the caller why.

InputTypeRequiredDefaultLimitsDescription
namestringyesThe place name as claimed, e.g. "The Eagle and Child".
claimed_timestringnoISO 8601 timestamp: when the itinerary claims you are at this place.
idstringnoCaller-chosen id, echoed back on the matching result. Auto-assigned ("claim-1", …) when omitted.
localitystringnoOptional disambiguating context, e.g. "Oxford". Country-level words ("UK", "England", …) are stripped before querying, they add noise, not signal, to name search.
sequenceintegernoItinerary position. Claims that share increasing sequence values and both carry claimed_time form legs the feasibility pass checks.

Output

FieldTypeAlways presentDescription
resultsVerifyClaimResult[]yes
summarystringyesAlways present: a concise plain-text summary alongside the structured results, several MCP clients (notably ChatGPT connectors) drop non-text content blocks, so this must stand on its own. This same string is also returned as the tool call's text content block, not only inside the structured JSON.

VerifyClaimResult: The result for one claim.

FieldTypeAlways presentDescription
claimVerifyClaimEchoyesThe claim as echoed back (a subset of the input, for context).
evidenceVerifyEvidence[]yes
idstringyes
verdictVerifyVerdictyesThe three-state verdict, see crate::verify module docs for why there is no fourth "does not exist" state and never a boolean.
feasibilityVerifyFeasibilitynoFeasibility of the leg arriving at this stop, when computable.
matchVerifyPlaceMatchnoThe place a claim matched, when one was found.

VerifyClaimEcho: The claim as echoed back (a subset of the input, for context).

FieldTypeAlways presentDescription
namestringyes
localitystringno
sequenceintegerno

VerifyEvidence: One piece of evidence backing a verdict. Always dated.

FieldTypeAlways presentDescription
checked_atstringyesISO 8601 timestamp of when this evidence was gathered (a check time, not necessarily the underlying map data's edit date).
detailstringyesShort factual note, always from a fixed template (see crate::verify), never free-form text that could assert non-existence or closure.
sourcestringyesWhere the evidence came from, e.g. "mapmap-geocode", "mapmap-matrix".

VerifyFeasibility: Feasibility of the leg arriving at this stop, when computable.

FieldTypeAlways presentDescription
available_minutesnumberyesMinutes the itinerary claims are available for this leg.
from_idstringyes
from_namestringyes
statusVerifyFeasibilityStatusyesFeasibility status of one itinerary leg.
to_idstringyes
to_namestringyes
travel_time_minutesnumbernoMinutes the routing engine computed, or null when it could not compute one at all (e.g. beyond its routable distance).

VerifyFeasibilityStatus: Feasibility status of one itinerary leg.

One of:

  • "feasible" \| "impossible"
  • "implausible": Tight, flagged, but not asserted as impossible (not certain enough to contradict).

VerifyPlaceMatch: The place a claim matched, when one was found.

FieldTypeAlways presentDescription
idstringyesStable identifier when the geocoding backend supplies one, otherwise a coordinate-based fallback.
latnumberyes
lonnumberyes
namestringyes
categorystringno

VerifyVerdict: The three-state verdict, see crate::verify module docs for why there is no fourth "does not exist" state and never a boolean.

ValueMeaning
"verified"Matched a real, findable place in the index.
"contradicted"A specific, dated, sourced fact contradicts the claim (currently: an itinerary leg the routing engine proves cannot be driven in the stated time). Never used to assert a business does not exist or has closed.
"unverified"No evidence either way: no confident name match, or the check could not run.

Example (called 2026-11-05)

json
{
  "claims": [
    {
      "name": "British Museum",
      "locality": "London"
    }
  ]
}

Result (trimmed):

json
{
  "results": [
    {
      "claim": {
        "locality": "London",
        "name": "British Museum"
      },
      "evidence": [
        "{…}"
      ],
      "id": "claim-1",
      "match": {
        "category": "house",
        "id": "latlon:51.51931,-0.12671",
        "lat": 51.5193118,
        "lon": -0.1267051,
        "name": "British Museum"
      },
      "verdict": "verified"
    }
  ],
  "summary": "1 claim checked: 1 verified, 0 contradicted, 0 unverified. claim-1 (\"British Mus…"
}

More from the server's description:

Claims sharing increasing sequence values and both carrying claimed_time (ISO 8601) form itinerary legs checked for feasibility via matrix, catching e.g. "breakfast in Bath, 10am meeting in Edinburgh". Max 20 claims per request. The response's summary field is a concise plain-text digest (also returned as this tool result's text content) so clients that drop structured/non-text content blocks still see the verdicts.

validate_geodata

Check whether a dataset's DECLARED coordinate reference system actually describes its own coordinates, before you draw it on a map.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • "EPSG:4326") and a sample of the raw coordinates as {x, y} in the dataset's OWN units, deliberately not named lon/lat, because whether they are degrees is the question.
  • This is a sanity check, not a reprojection: it never transforms coordinates.

Inputs

InputTypeRequiredDefaultLimitsDescription
coordinatesXY[]yesA sample of the dataset's coordinates. A few dozen is plenty; the check is about ranges and spans, not volume.
declared_crsstringyesThe CRS the dataset claims, e.g. "EPSG:4326", "EPSG:32610", "EPSG:3857", "EPSG:27700".

XY: One coordinate from the dataset, in the dataset's own units, NOT necessarily degrees. Named x/y rather than lon/lat precisely because whether they are degrees is the thing in question.

InputTypeRequiredDefaultLimitsDescription
xnumberyes
ynumberyes

Output

FieldTypeAlways presentDescription
extentExtentyesObserved extent of the sample, in the dataset's own units.
interpreted_asstringyesThe CRS family the declaration was understood as.
problemsstring[]yesPlain-language findings, most important first. Empty when consistent.
suggestionsstring[]yesWhat the numbers look like, when they do not match the declaration.
verdictVerdictyesHow much the declared CRS and the coordinates disagree.

Extent

FieldTypeAlways presentDescription
max_xnumberyes
max_ynumberyes
min_xnumberyes
min_ynumberyes

Verdict: How much the declared CRS and the coordinates disagree.

ValueMeaning
"consistent"Coordinates are consistent with the declared CRS.
"suspect"Consistent only under an assumption worth stating (e.g. the values fit, but the axis order looks swapped).
"impossible"The declared CRS cannot describe these coordinates at all.

Example (called 2026-11-05)

json
{
  "declared_crs": "EPSG:4326",
  "coordinates": [
    {
      "x": 51.5,
      "y": -0.12
    },
    {
      "x": 51.6,
      "y": -0.2
    }
  ]
}

Result (trimmed):

json
{
  "extent": {
    "max_x": 51.6,
    "max_y": -0.12,
    "min_x": 51.5,
    "min_y": -0.2
  },
  "interpreted_as": "geographic degrees",
  "problems": [],
  "suggestions": [],
  "verdict": "consistent"
}

More from the server's description:

Catches the failures that are otherwise silent: swapped lat/lon axes, degrees labelled as metres, and Web Mercator or another projection mislabelled with a UTM or national-grid code. Pass the declared CRS (e.g. Returns a verdict (consistent / suspect / impossible), what is wrong in plain language, and where the numbers actually point when read another way. Local computation: no network call, no quota.

Map styling

Read, create and restyle hosted map styles, and audit their contrast.

list_style_layers

List everything a MapMap style theme can style: the first-party named bases a style can start from (pass one as create_style's base, or as the static-map API's style=), the named palette slots with their light/dark default colours, the skeleton layer ids (paint order) that set_layer_paint and set_layer_layout accept, what the less obvious layers draw (the footpath layers road-path and road-path-* for footways, cycleways, steps, bridleways, pedestrian streets and tracks, pedestrian squares pedestrian-areas) and the slot that colours each, the OpenMapTiles source-layers extra layers may reference, the insertion slots (bottom, middle, top) an extra layer may name, and the rules for a theme's own sources (your data by https URL).

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • Attribution is enforced on every compiled style and cannot be themed away.

Inputs

No inputs: call it with {}.

Output

FieldTypeAlways presentDescription
attributionstringyesThe attribution contract: enforced on every compiled style, not themable.
basesStyleBaseInfo[]yesThe first-party named bases a style can start from, in display order: pass one to create_style's base, or to the static-map API's style=.
layer_idsstring[]yesThe skeleton layer ids a theme's layers may override, in paint order (first = bottom).
palette_slotsPaletteSlotInfo[]yesThe named palette slots a theme's palette may override, with their light/dark defaults, in presentation order.
source_layersstring[]yesThe OpenMapTiles source-layers the tiles emit; an extra layer on the territory source must reference one of these.
layer_descriptionsStyleLayerDescription[]noWhat a layer draws and the palette slot that colours it, for the layers whose id alone does not say (footpaths and pedestrian squares).
slotsStyleSlotInfo[]noThe insertion slots an extra layer may name ("slot"), bottom of the stack first, with the layer each sits directly below. A layer without a slot goes in middle.
sourcesStyleSourcesInfonoThe rules for a theme's own sources (customer data by URL).

StyleBaseInfo: One entry in the named-base catalogue (sn_style::list_bases).

FieldTypeAlways presentDescription
descriptionstringyesOne line saying what the base is for. This is what to choose on.
groupstringyes"base" for the two stock slates (light, dark), "designed" for a drawn style.
idstringyesBase id: what create_style's base and the static-map API's style= accept, e.g. "navigator-night".
labelstringyesShort display label, e.g. "Navigator Night".
restrictionsbooleanyesTrue when the base is drawn to sit under MapMap's vehicle-restriction overlay (only fleet). That overlay is a mapmap.ai runtime layer, not tile data and not part of any compiled style, so it draws in Studio and on /map and nowhere else. Asking for this base anywhere else gets the palette alone; for a real restriction answer use check_clearance_on_route or check_adr_tunnel, which answer for an actual vehicle.

StyleLayerDescription: What one skeleton layer draws.

FieldTypeAlways presentDescription
drawsstringyesWhat it draws, in words.
layer_idstringyesThe skeleton layer id, e.g. "road-path".
palette_slotstringyesThe palette slot that colours it, e.g. "path".

PaletteSlotInfo: One themable palette slot with its built-in light/dark defaults.

FieldTypeAlways presentDescription
darkstringyesDefault colour on the dark base theme.
lightstringyesDefault colour on the light base theme.
slotstringyesSlot name, e.g. "water" or "roadMajor".

StyleSlotInfo: One insertion slot for extra layers (Mapbox Standard names).

FieldTypeAlways presentDescription
beforestringyesThe skeleton layer id the slot sits directly below.
descriptionstringyesWhere it sits, in words.
slotstringyesbottom, middle or top.

StyleSourcesInfo: What a theme's sources block accepts.

FieldTypeAlways presentDescription
max_geojson_sourcesintegeryesAt most this many of them geojson.
max_sourcesintegeryesAt most this many sources per theme.
typesstring[]yesAccepted source types.
url_rulesstringyesThe URL rules every source URL must pass.

Example (called 2026-11-05)

json
{}

Result (trimmed):

json
{
  "attribution": "\"© OpenStreetMap contributors © OpenMapTiles\" is stamped on every compiled style…",
  "bases": [
    {
      "description": "The default light base, a clean slate.",
      "group": "base",
      "id": "light",
      "label": "Light",
      "restrictions": false
    },
    {
      "description": "The default dark base, a clean slate.",
      "group": "base",
      "id": "dark",
      "label": "Dark",
      "restrictions": false
    },
    "… 9 more"
  ],
  "layer_descriptions": [
    {
      "draws": "Pedestrian squares, plazas and footway areas (transportation polygons of class p…",
      "layer_id": "pedestrian-areas",
      "palette_slot": "pedestrianArea"
    },
    {
      "draws": "Non-drivable ways that have no layer of their own (class path with subclass path…",
      "layer_id": "road-path",
      "palette_slot": "path"
    },
    "… 6 more"
  ],
  "layer_ids": [
    "background",
    "landcover",
    "… 60 more"
  ],
  "palette_slots": [
    {
      "dark": "#12161c",
      "light": "#f4f2ee",
      "slot": "background"
    },
    {
      "dark": "#1b2c40",
      "light": "#a8c8e8",
      "slot": "water"
    },
    "… 30 more"
  ],
  "slots": [
    {
      "before": "building",
      "description": "above land, landuse and water; below roads and buildings",
      "slot": "bottom"
    },
    {
      "before": "road-oneway",
      "description": "above roads and 3D buildings; below every label (the default)",
      "slot": "middle"
    },
    "… 1 more"
  ],
  "source_layers": [
    "aerodrome_label",
    "aeroway",
    "… 14 more"
  ],
  "sources": {
    "max_geojson_sources": 8,
    "max_sources": 15,
    "types": [
      "geojson",
      "vector",
      "… 2 more"
    ],
    "url_rules": "https:// only (pmtiles://https:// for a vector or raster archive url); no user n…"
  }
}

More from the server's description:

Local lookup, no network; always works.

get_style

Fetch a hosted style's latest theme document (the editable source) and the URL of its latest compiled MapLibre style.

Access: Keyless. Public style read: no key, never metered.

Inputs

InputTypeRequiredDefaultLimitsDescription
style_idstringyesHosted style id, e.g. "midnight-fleet-a1b2c3".

Output

FieldTypeAlways presentDescription
style_idstringyesHosted style id.
style_urlstringyesURL of the latest compiled MapLibre style (point MapLibre GL at it).
themeany JSONyesThe latest theme document, the editable source the next version is published from.

Example (called 2026-11-05)

json
{
  "style_id": "mapmap-light-76ef8d"
}

Result (trimmed):

json
{
  "style_id": "mapmap-light-76ef8d",
  "style_url": "https://api.mapmap.ai/styles/mapmap-light-76ef8d.json",
  "theme": {
    "base": "light",
    "name": "MapMap Light"
  }
}

More from the server's description:

Use the theme to inspect current palette and layer overrides before editing.

create_style

Create a hosted map style. Provide a name, optionally a named base to start from (light, dark, streets, midnight, navigator-day, navigator-night, fleet, outdoor, dataviz, backdrop, print - call list_style_layers for what each one is for), and optionally a theme document ({base: "light"|"dark", palette: {slot: colour}, layers: {layer_id: overrides}}).

Access: Your own key. Metered publish. Refused on the hosted endpoint unless you present your own snk_ key; a self-hosted server publishes with its own STUDIO_API_KEY.

Caveats the server states:

  • A theme may also set worldview (ISO 3166-1 alpha-2, accepted: AE, KR, SA, US) to display that jurisdiction's official names for a small curated registry of renamed features; omitted, labels keep the OSM on-the-ground names.

Inputs

InputTypeRequiredDefaultLimitsDescription
namestringyesHuman-readable style name; its kebab-case slug seeds the style id.
basestringnolight theme as it always didOptional named base to start from, e.g. "streets", "midnight", "navigator-night", "fleet". Call list_style_layers for the catalogue, including which bases expect an overlay of their own. The base's whole document is laid down first and theme is merged over it, so a base plus two palette entries is a complete style. Omitted, the style starts from the default light theme as it always did.
themeany JSONnotheme outrightOptional theme document (palette/layer overrides). Merged over base when one is given; on its own, it replaces the default theme outright.

Output

FieldTypeAlways presentDescription
style_idstringyesThe generated style id, pass it to set_palette, set_layer_paint and get_style.
style_urlstringyesImmutable URL of the compiled style at this version.
themeany JSONyesThe theme document that was stored.
versionintegeryesThe published version (always 1 on create).

Example

json
{
  "name": "Streets, darker water",
  "base": "streets",
  "theme": {
    "palette": {
      "water": "#14243a"
    }
  }
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

The named base is laid down first and the theme document is merged over it, so "streets with darker water" is one call; with neither, the style starts from the default theme. Returns the generated style_id (pass it to set_palette / set_layer_paint) and the compiled style URL for MapLibre.

set_palette

Recolour one or more palette slots of a hosted style (e.g. {"water": "#0b2038", "roadMajor": "#8a6d3b"}) and publish the result as a new immutable style version.

Access: Your own key. Metered publish. Refused on the hosted endpoint unless you present your own snk_ key; a self-hosted server publishes with its own STUDIO_API_KEY.

Inputs

InputTypeRequiredDefaultLimitsDescription
coloursmap of stringyesPalette overrides: slot name → CSS colour (e.g. {"water": "#0b2038"}). Call list_style_layers for the slot names.
style_idstringyesHosted style id.

Output

FieldTypeAlways presentDescription
style_idstringyesHosted style id.
style_urlstringyesImmutable URL of the compiled style at this version.
versionintegeryesThe newly published version.

Example

json
{
  "style_id": "midnight-fleet-a1b2c3",
  "colours": {
    "water": "#0b2038"
  }
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

Slot names come from list_style_layers; colours are CSS (#rgb/#rrggbb/#rrggbbaa/rgb()/hsl()). Returns the new version and style URL.

set_layer_paint

Set one MapLibre paint property on one skeleton layer of a hosted style (e.g. layer_id "road-major", property "line-width", value 4 or an expression array; null removes the property, e.g. line-dasharray null draws the dashed road-path solid) and publish the result as a new immutable style version.

Access: Your own key. Metered publish. Refused on the hosted endpoint unless you present your own snk_ key; a self-hosted server publishes with its own STUDIO_API_KEY.

Inputs

InputTypeRequiredDefaultLimitsDescription
layer_idstringyesSkeleton layer id, e.g. "road-major". Call list_style_layers for the accepted ids.
propertystringyesMapLibre paint property name, e.g. "line-width" or "fill-color".
style_idstringyesHosted style id.
valueany JSONyesThe paint value (any MapLibre-valid JSON: number, colour string or expression array), or null to remove the property (for example line-dasharray null draws a dashed skeleton layer solid).

Output

FieldTypeAlways presentDescription
style_idstringyesHosted style id.
style_urlstringyesImmutable URL of the compiled style at this version.
versionintegeryesThe newly published version.

Example

json
{
  "style_id": "midnight-fleet-a1b2c3",
  "layer_id": "road-path",
  "property": "line-dasharray",
  "value": null
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

Layer ids come from list_style_layers. Returns the new version and style URL.

set_layer_layout

Set one MapLibre layout property on one skeleton layer of a hosted style (e.g. layer_id "road-path", property "line-cap", value "round"; null removes the property) and publish the result as a new immutable style version.

Access: Your own key. Metered publish. Refused on the hosted endpoint unless you present your own snk_ key; a self-hosted server publishes with its own STUDIO_API_KEY.

Inputs

InputTypeRequiredDefaultLimitsDescription
layer_idstringyesSkeleton layer id, e.g. "road-path". Call list_style_layers for the accepted ids.
propertystringyesMapLibre layout property name, e.g. "line-cap", "line-join" or "visibility".
style_idstringyesHosted style id.
valueany JSONyesThe layout value (any MapLibre-valid JSON, e.g. "round"), or null to remove the property and fall back to the skeleton's.

Output

FieldTypeAlways presentDescription
style_idstringyesHosted style id.
style_urlstringyesImmutable URL of the compiled style at this version.
versionintegeryesThe newly published version.

Example

json
{
  "style_id": "midnight-fleet-a1b2c3",
  "layer_id": "road-path",
  "property": "line-cap",
  "value": "round"
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

Pair it with set_layer_paint for line patterns: dotted footpaths are road-path line-dasharray [0, 2] plus line-cap round. Layer ids come from list_style_layers. Returns the new version and style URL.

check_style_contrast

Audit a map style's colour contrast against WCAG 2.1 (4.5:1 for label text, 3:1 for graphics like the route line), across both the light and dark palette variants.

Access: Keyless, local. Local check, no key, never metered; with a style_id it also reads that hosted style (a public read).

Caveats the server states:

  • Advisory: failing pairs list the palette slots to adjust with set_palette; publishing is never blocked on contrast.

Inputs

InputTypeRequiredDefaultLimitsDescription
style_idstringnoHosted style id whose latest theme should be checked…
themeany JSONno…or an inline theme document (as accepted by create_style). Exactly one of style_id/theme must be given.

Output

FieldTypeAlways presentDescription
findingsContrastFindingInfo[]yesEvery audited pair, failing pairs first.
passesbooleanyesTrue when every audited pair meets its WCAG threshold.

ContrastFindingInfo: One audited colour pair of a contrast report.

FieldTypeAlways presentDescription
backgroundstringyesEffective backing palette slot.
descriptionstringyesWhy this pair matters cartographically.
foregroundstringyesForeground palette slot.
kindstringyesPair kind: "text" (threshold 4.5) or "graphics" (threshold 3.0).
passesbooleanyesWhether the pair meets its threshold.
rationumberyesMeasured WCAG 2.1 contrast ratio.
thresholdnumberyesThe WCAG threshold applied.
variantstringyesPalette variant audited: "light" or "dark".

Example (called 2026-11-05)

json
{
  "style_id": "mapmap-light-76ef8d"
}

Result (trimmed):

json
{
  "findings": [
    {
      "background": "water",
      "description": "water names on the water fill",
      "foreground": "textSecondary",
      "kind": "text",
      "passes": false,
      "ratio": 3.07,
      "threshold": 4.5,
      "variant": "light"
    },
    {
      "background": "roadMajor",
      "description": "road labels on the major-road fill",
      "foreground": "textSecondary",
      "kind": "text",
      "passes": false,
      "ratio": 3.9,
      "threshold": 4.5,
      "variant": "light"
    },
    "… 14 more"
  ],
  "passes": false
}

More from the server's description:

Pass a hosted style_id OR an inline theme document (as accepted by create_style).

Geo helpers

Local geodesic geometry on WGS84: no network, no quota.

geo_distance

Distance in metres between two coordinates. Computed geodesically on the WGS84 ellipsoid, so it is the straight-line (as-the-crow-flies) distance, NOT a driving distance, use route or matrix for travel distance and time.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
fromLatLonyesStart coordinate.
toLatLonyesEnd coordinate.

Output

FieldTypeAlways presentDescription
distance_mnumberyesDistance in metres along the WGS84 ellipsoid.

Example (called 2026-11-05)

json
{
  "from": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "to": {
    "lat": 51.5308,
    "lon": -0.1238
  }
}

Result (trimmed):

json
{
  "distance_m": 3349.3302497533177
}

More from the server's description:

Local computation: no network call, no quota.

geo_bearing

Initial bearing from one coordinate to another, in degrees clockwise from true north (0-360).

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
fromLatLonyesStart coordinate.
toLatLonyesEnd coordinate.

Output

FieldTypeAlways presentDescription
bearing_degnumberyesInitial bearing from from to to, degrees clockwise from true north, normalised to 0–360. Note this is the bearing at the start of the geodesic: over long distances the bearing changes en route.

Example (called 2026-11-05)

json
{
  "from": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "to": {
    "lat": 51.5308,
    "lon": -0.1238
  }
}

Result (trimmed):

json
{
  "bearing_deg": 0.9497178516508029
}

More from the server's description:

This is the bearing at the START of the geodesic; over long distances the bearing changes en route. Local computation: no network call, no quota.

geo_destination

The coordinate reached by travelling distance_m metres from from on bearing_deg (degrees clockwise from true north).

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
bearing_degnumberyesBearing in degrees clockwise from true north.
distance_mnumberyesDistance to travel in metres.
fromLatLonyesStarting coordinate.

Output

FieldTypeAlways presentDescription
pointLatLonyesThe computed coordinate.

Example (called 2026-11-05)

json
{
  "from": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "bearing_deg": 90,
  "distance_m": 1000
}

Result (trimmed):

json
{
  "point": {
    "lat": 51.500699115986265,
    "lon": -0.11019896505598184
  }
}

More from the server's description:

The inverse of geo_distance + geo_bearing. Local computation: no network call.

geo_point_in_polygon

Whether a coordinate lies inside a polygon: delivery zones, catchments, congestion or clean-air zones, site boundaries.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • Provide point {lat, lon} and polygon as 3+ {lat, lon} coordinates of the outer ring (closed automatically if the last does not repeat the first).

Inputs

InputTypeRequiredDefaultLimitsDescription
pointLatLonyesThe coordinate to test.
polygonLatLon[]yesThe polygon's outer ring, 3 or more coordinates. Closed automatically if the last point does not repeat the first.

Output

FieldTypeAlways presentDescription
insidebooleanyesTrue when the point is strictly inside the ring. Points exactly on the boundary are not counted as inside.

Example (called 2026-11-05)

json
{
  "point": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "polygon": [
    [
      -0.13,
      51.49
    ],
    [
      -0.11,
      51.49
    ],
    [
      -0.11,
      51.51
    ],
    [
      -0.13,
      51.51
    ]
  ]
}

Result (trimmed):

json
{
  "inside": true
}

More from the server's description:

Points exactly on the boundary count as OUTSIDE. Local computation: no network call, no quota.

geo_bbox

The axis-aligned bounding box enclosing 1+ coordinates, as {min_lat, min_lon, max_lat, max_lon}.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
pointsLatLon[]yesThe coordinates to consider.

Output

FieldTypeAlways presentDescription
max_latnumberyesMaximum latitude (north edge).
max_lonnumberyesMaximum longitude (east edge).
min_latnumberyesMinimum latitude (south edge).
min_lonnumberyesMinimum longitude (west edge).

Example (called 2026-11-05)

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ]
}

Result (trimmed):

json
{
  "max_lat": 51.5308,
  "max_lon": -0.1238,
  "min_lat": 51.5007,
  "min_lon": -0.1246
}

More from the server's description:

Useful for fitting a map view to a set of stops. Local computation: no network call, no quota.

geo_centroid

The centroid (geometric mean position) of 1+ coordinates, e.g. to pick a depot location or centre a map.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
pointsLatLon[]yesThe coordinates to consider.

Output

FieldTypeAlways presentDescription
pointLatLonyesThe computed coordinate.

Example (called 2026-11-05)

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ]
}

Result (trimmed):

json
{
  "point": {
    "lat": 51.51575,
    "lon": -0.1242
  }
}

More from the server's description:

Local computation: no network call, no quota.

geo_length

Total length in metres of a polyline through 2+ coordinates, summed geodesically. This measures the line you supply, NOT a driven route , use route for that.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
pointsLatLon[]yesThe coordinates to consider.

Output

FieldTypeAlways presentDescription
length_mnumberyesTotal geodesic length in metres.

Example (called 2026-11-05)

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ]
}

Result (trimmed):

json
{
  "length_m": 3349.3302497533177
}

More from the server's description:

Local computation: no network call, no quota.

geo_area

Area in square metres enclosed by a ring of 3+ coordinates, computed geodesically. Always positive: the answer does not depend on whether the ring is wound clockwise or anticlockwise.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • Intended for zones and boundaries, not for polygons covering more than half the globe.

Inputs

InputTypeRequiredDefaultLimitsDescription
pointsLatLon[]yesThe coordinates to consider.

Output

FieldTypeAlways presentDescription
area_sq_mnumberyesEnclosed area in square metres, always positive regardless of winding order.

Example (called 2026-11-05)

json
{
  "points": [
    [
      -0.13,
      51.49
    ],
    [
      -0.11,
      51.49
    ],
    [
      -0.11,
      51.51
    ]
  ]
}

Result (trimmed):

json
{
  "area_sq_m": 1545498.4211921692
}

More from the server's description:

Local computation: no network call, no quota.

geo_simplify

Reduce the number of coordinates in a polyline while keeping its shape (Douglas-Peucker). tolerance_deg is in DEGREES, not metres: about 0.0001 drops detail finer than roughly 10 m at the equator.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Inputs

InputTypeRequiredDefaultLimitsDescription
pointsLatLon[]yesThe polyline's coordinates, 2 or more.
tolerance_degnumberyesDouglas-Peucker tolerance in degrees, not metres. Around 0.0001 drops detail finer than roughly 10 m at the equator.

Output

FieldTypeAlways presentDescription
pointsLatLon[]yesThe retained coordinates, endpoints always preserved.
removedintegeryesHow many coordinates were removed.

Example (called 2026-11-05)

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.51,
      "lon": -0.1245
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ],
  "tolerance_deg": 0.001
}

Result (trimmed):

json
{
  "points": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ],
  "removed": 1
}

More from the server's description:

Endpoints are always kept. Returns the retained points and how many were removed. Local computation: no network call, no quota.

geo_nearest_point_on_line

The closest position on a polyline to a given coordinate, plus the geodesic distance to it in metres.

Access: Keyless, local. Local computation: no key, never metered, answers on any transport.

Caveats the server states:

  • The answer may lie between vertices, not only on them.

Inputs

InputTypeRequiredDefaultLimitsDescription
lineLatLon[]yesThe polyline's coordinates, 2 or more.
pointLatLonyesThe coordinate to measure from.

Output

FieldTypeAlways presentDescription
distance_mnumberyesGeodesic distance from the input point to that position, metres.
pointLatLonyesThe closest position on the line, which may lie between vertices.

Example (called 2026-11-05)

json
{
  "point": {
    "lat": 51.515,
    "lon": -0.12
  },
  "line": [
    {
      "lat": 51.5007,
      "lon": -0.1246
    },
    {
      "lat": 51.5308,
      "lon": -0.1238
    }
  ]
}

Result (trimmed):

json
{
  "distance_m": 292.99648720985147,
  "point": {
    "lat": 51.515112078530855,
    "lon": -0.12421695472343242
  }
}

More from the server's description:

Useful for 'how far is this address from the route?'. Local computation: no network call, no quota.

Jobs

The asynchronous lane for problems too large for one request.

submit_optimise_job

Submit a problem too large to solve inside one request to the asynchronous lane, and get a job id back.

Access: Keyless. Metered on submission (refunded if the job fails). Not called in the live check, because it queues work.

Caveats the server states:

  • Set kind to "optimise", "replan", "matrix" or "coverage".
  • A field that tool's input does not have is REFUSED by name rather than dropped: the HTTP API accepts some the MCP tools have not surfaced yet, and a job queued without a constraint you asked for is worse than one that was never queued.
  • "coverage" plans one drive that passes along EVERY street in an area at least once, in the legal direction, split into outings (the Chinese postman problem on the real road network): survey capture, street sweeping, gritting, refuse rounds.
  • It has no synchronous tool; its problem is the POST /v1/jobs/coverage body: exactly one of bbox ([min_lon, min_lat, max_lon, max_lat]) or polygon (GeoJSON), and optionally costing ("auto" or "truck"), optimise ("distance", "time" or "balanced"), road_classes {include, exclude}, include_private, turn_penalties {avoid_left, avoid_right, avoid_u_turn} in metres per turn, chunk_km (outing length, default 15), start {lat, lon}, return_to_start, chunk_loops, loop_radius_m, exclude and avoid.
  • loop_radius_m (0 to 2,000 m, only with chunk_loops) lets an outing end within that distance of its start when that saves driving.
  • exclude.way_ids lists OpenStreetMap way ids not to cover, taken from the way_id of a previous plan's steps; exclude.areas is a GeoJSON polygon, and a street more than half inside it is not covered.
  • avoid is a hard block, never driven, not even to connect streets or between outings: avoid.way_ids closes whole ways both ways; avoid.locations [{lat, lon, radius_m?, heading?, heading_tolerance?}] closes the street a point is on (never the cross street; within 3 m of a junction it needs heading, else it closes nothing and comes back in summary.avoid_unmatched), one direction with heading, or every street within radius_m (0 to 100).
  • After planning, every turn the graph forbids and every U-turn where a street merely continues, on a bridge or on a tertiary or bigger road is replaced by the cheapest legal way round (deadhead); other U-turns not at a dead end are replaced when that costs less than avoid_u_turn.
  • The result (from get_job) has chunks (the outings) with ordered steps (way_id, name, deadhead) and a ready trace_route body each, plus trace_route_parts, the same replay cut into pieces that never pass a junction twice (the engine's exact edge walk can refuse a drive that comes back through a junction as one piece; the pieces it walks).
  • A client turns one outing or transfer into turn-by-turn guidance with one call to the HTTP API's POST /v1/coverage/guidance (job id and outing id): one trip, one leg per piece, never map matched.
  • And a summary including dropped and excluded streets and restricted_turns (only the restricted turns and refused U-turns no legal way round could replace, with where and why; summary.turns counts the ones replaced and the km they added).
  • A re-plan is counted on the REMAINING problem, after completed stops are removed, so a shift well through its day may fit where the morning's would not.
  • This answers 202-and-a-job-id, NOT a plan: the job is queued and a worker picks it up.
  • Polling is free, the gateway meters this submission, not the reads.
  • The optional webhook_url (https only) posts a SIGNED notification when the job finishes and is for a human wiring infrastructure that must react without a process watching; it carries a pointer, never the result, and needs a webhook signing secret on the key.
  • An agent that can poll should not use it.

Inputs

InputTypeRequiredDefaultLimitsDescription
kindJobKindyesWhich problem this is. It selects both the body shape below and the ceiling the submission is judged against.
problemany JSONyesThe problem itself, in exactly the shape the synchronous tool takes , optimise_routes input for optimise, replan_routes input for replan, matrix input for matrix. Moving a working synchronous call onto this lane changes nothing but the tool you call it with. coverage has no synchronous tool: its problem is the POST /v1/jobs/coverage body (bbox or polygon, costing, optimise, road_classes, include_private, turn_penalties, chunk_km, start, return_to_start, chunk_loops, loop_radius_m, exclude, avoid), without webhook_url, which is this tool's own field.
webhook_urlstringnoOptional HTTPS URL to POST a signed {job_id, kind, status, result_url} notification to when the job finishes. The RESULT is never pushed, the notification says where to fetch it. Requires a webhook signing secret on the key; without one the submission is refused rather than delivered unsigned. An agent that can poll does not need this: polling with get_job is free.

JobKind: Which asynchronous problem is being submitted.

ValueMeaning
"optimise"A fleet optimisation, the optimise_routes problem, at ten times the synchronous location cap.
"replan"A mid-shift re-plan, the replan_routes problem, counted on the remaining work.
"matrix"A many-to-many time/distance matrix, the matrix problem, at four times the synchronous element cap.
"coverage"A drive-every-street plan (the Chinese postman problem on the road network): one drive along every street in an area, split into outings. There is no synchronous tool for it, so problem is the POST /v1/jobs/coverage body itself, see CoverageProblem.

Output

FieldTypeAlways presentDescription
idstringyesThe job id. Pass it to get_job to poll.
kindstringyesoptimise, replan, matrix or coverage.
nextstringyesWhat to do next, in one sentence: poll get_job, and how often is reasonable.
result_urlstringyesThe HTTP URL this job (and its result) can be fetched from. The same URL a webhook carries. get_job is the tool that reads it.
statusstringyesAlways queued, no worker has looked at it yet.
units_chargedintegeryesQuota units this submission drew. Charged now, handed back in full if the job fails.

Example

json
{
  "kind": "matrix",
  "problem": {
    "origins": [
      {
        "lat": 51.5007,
        "lon": -0.1246
      }
    ],
    "destinations": [
      {
        "lat": 51.5308,
        "lon": -0.1238
      }
    ],
    "costing": "auto"
  }
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

For the first three, pass problem in EXACTLY the shape the matching synchronous tool takes, optimise_routes input, replan_routes input, or matrix input. Moving a working synchronous call onto this lane changes nothing but which tool you call it with. chunk_loops: true makes every outing a closed loop that starts and ends at the same junction, with the drive between outings planned as a separate route (each chunk's transfer_from_previous, plus transfer_to_start with return_to_start); walk_km excludes those transfers (summary.transfer_km has them), and summary.outings_outside_target names any outing outside 0.75 to 1.25 times chunk_km and why. An excluded street may still be driven to connect others, as a deadhead step. Ids that no longer match the graph are listed back in the result's summary.exclude_unmatched_way_ids. Streets to cover that a block closes or cuts off are listed in summary.dropped_ways with a reason (avoided, unreachable_after_avoid, no_legal_loop). Billed per started square kilometre: charged on the area's bounding box, settled on success to the polygon's own area or its street floor, whichever is larger (the result's billing); the gateway's area, edge and time limits apply. The ceilings are far higher here because there is no request to hold open: 2,000 unique locations for an optimisation or re-plan against the synchronous 200, and 40,000 matrix elements against 10,000 (a deployment may set either lower, in which case its own refusal is the authority). Poll get_job with the returned id until it says the status is terminal, then read the result. Units are charged on submission and handed back in full if the job fails. Requires the MapMap gateway.

get_job

Read an asynchronous job submitted with submit_optimise_job: its status, and once it has finished, its result inline, exactly the body the synchronous tool would have returned.

Access: Keyless. Answers without a key on the hosted endpoint; polling is never metered. Another key's job reads as not found.

Caveats the server states:

  • POLLING IS FREE: the gateway meters the submission and not the reads, deliberately, because a poll that costs quota is a poll a caller rations, and a rationed poll is how a job that finished in ten seconds gets noticed four minutes later.
  • Webhooks are the alternative to polling and exist for humans wiring infrastructure, not for agents in a loop.
  • A job belongs to the key that submitted it (or another key of the same identity); anyone else's id answers NOT FOUND rather than forbidden, because confirming an id exists is itself a disclosure.

Inputs

InputTypeRequiredDefaultLimitsDescription
idstringyesThe job id returned by submit_optimise_job.

Output

FieldTypeAlways presentDescription
created_atstringyesRFC 3339 UTC submission time.
idstringyesThe job id.
kindstringyesoptimise, replan, matrix or coverage.
refundedbooleanyesWhether a failure refunded the submission's units.
statusstringyesqueued, running, succeeded or failed. Only succeeded and failed are terminal; keep polling on the other two.
terminalbooleanyesWhether status is one this job will never leave.
units_chargedintegeryesMetered units the SUBMISSION drew. This is what was charged; refunded says whether it came back.
errorstringnoWhy it failed, once status is failed.
finished_atstringnoRFC 3339 UTC time it finished, either way.
resultany JSONnoThe answer, inline, once status is succeeded, exactly the body the synchronous tool would have returned (for coverage, which has none, the gateway's plan unchanged). A matrix job answers durations_s and distances_m, as the matrix tool does; it also still carries the same numbers as durations and distances, which are DEPRECATED and will be removed in a future release.
started_atstringnoRFC 3339 UTC time a worker picked it up.
webhook_statusstringnodelivered or delivery_failed, once a webhook was attempted.

Example (called 2026-11-05)

json
{
  "id": "job_does_not_exist_docs_audit"
}

The hosted endpoint answered with this tool error:

text
gateway /v1/jobs/job_does_not_exist_docs_audit answered HTTP 404: not found: no job job_does_not_exist_docs_audit

More from the server's description:

Status is queued, running, succeeded or failed; the answer's terminal field says whether the job will ever leave the status it is in, so poll while that is false. Check every few seconds rather than guessing at a duration. units_charged is what the SUBMISSION drew, and refunded says whether a failure handed it back, a failed job shows both, because reporting zero would be a lie about what was charged. Requires the MapMap gateway.

Account and usage

What your own key has spent.

get_usage

Check what your own API key has spent, so you can decide mid-task whether to keep going. Reading it is free: it costs no quota.

Access: Your own key. Needs your own key (snk_ or an OAuth sno_ token): it reports on the key that authenticates the call and refuses keyless calls on the hosted endpoint. Reading it costs no quota.

Caveats the server states:

  • Everything is counted in UNITS, weighted quota units, where a heavier endpoint costs more than one unit per request, so never report these figures as a number of calls.
  • When identity_pooled is true the quota is shared with the other keys belonging to the same owner, so these figures are not yours alone.
  • On the hosted endpoint, connect with your own key (a bearer snk_ key or OAuth sign-in): an anonymous connection is refused rather than shown the server operator's usage.

Inputs

InputTypeRequiredDefaultLimitsDescription
fromstringnothe first day of the current monthFirst day to report, YYYY-MM-DD (UTC) inclusive. Defaults to the first day of the current month. At most 92 days may separate from and to.
tostringnotodayLast day to report, YYYY-MM-DD (UTC) inclusive. Defaults to today.

Output

FieldTypeAlways presentDescription
balance_millipenceintegeryesPrepaid balance in millipence (thousandths of a penny), for usage beyond the monthly allowance.
daysUsageDay[]yesPer-day breakdown over the range, oldest first. A day with no usage is absent rather than reported as zero.
fromstringyesFirst day covered, YYYY-MM-DD inclusive.
identity_pooledbooleanyesWhether quota is pooled across every key belonging to the same identity. When true, these figures are the identity's shared consumption, so another key of the same owner also spends them.
key_idstringyesIdentifier of the key this usage belongs to.
month_used_unitsintegeryesUnits counted against the monthly quota right now: the very number the quota check enforces on, independent of from/to. Quota is monthly, so this (not total_units) is what to compare with monthly_quota_units. It includes units recorded but not yet written to the daily counters, so over a whole-month range it can exceed total_units; that is not a discrepancy.
monthly_quota_unitsintegeryesThe key's monthly allowance in units.
tostringyesLast day covered, YYYY-MM-DD inclusive.
total_unitsintegeryesTotal units over the whole range.
totalsUsageEndpointTotal[]yesUnits per endpoint over the whole range.

UsageDay: One day's usage, in units per endpoint.

FieldTypeAlways presentDescription
daystringyesThe day, YYYY-MM-DD.
endpointsUsageEndpointTotal[]yesUnits per endpoint on this day, heaviest first.
total_unitsintegeryesTotal units on this day.

UsageEndpointTotal: Units attributed to one endpoint.

FieldTypeAlways presentDescription
endpointstringyesThe endpoint label the meter records, e.g. /route, /geocode, /tiles. Labels are collapsed by the meter, so several request shapes can share one.
unitsintegeryesWeighted quota units, never a count of calls.

Example (called 2026-11-05)

json
{}

Answers with your own key's usage, so no result is shown here.

More from the server's description:

Optional from and to (YYYY-MM-DD UTC, inclusive, at most 92 days apart) bound the report; omitted, it covers the current month to date. Returns days (per-day, per-endpoint), totals per endpoint over the range, total_units, plus month_used_units against monthly_quota_units and the prepaid balance_millipence (thousandths of a penny). The key that authenticates the call is the key reported on: there is no way to read another caller's usage. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).

Feedback and operator tools

Map corrections, integration retros and the operator-only closure write.

report_map_issue

Report that the live world disagrees with the map, a closed road, a wrong or missing restriction, a bad speed limit, a missing road, a wrong one-way, or changed access.

Access: Keyless. Appends to the server's local review queue (SN_MAP_ISSUES_DIR). Not called in the live check, because it writes.

Caveats the server states:

  • This is a first-party observation: it is QUEUED for human/agent review and NEVER changes routing immediately or edits any map.

Inputs

InputTypeRequiredDefaultLimitsDescription
categoryMapIssueCategoryyesWhat kind of mismatch this is.
locationLatLonyesWhere the mismatch was observed (WGS84 decimal degrees).
descriptionstringnoFree-text detail of what was observed on the ground, e.g. "barrier across the lane, diversion signed via the B4009". Bounded length.
evidence_urlstringnoA URL backing the observation (photo, notice, news item), when one exists. Bounded length.
way_idintegernoThe OSM way id the observation concerns, when the caller knows it. Optional, the report stands on its own as a first-party observation and is never tied to OSM data beyond this hint.

MapIssueCategory: The kind of map/live-world mismatch an agent is reporting. A first-party observation only: it records what the agent saw on the ground, never an edit to any map or OSM-derived database.

ValueMeaning
"road_closed"A road the map shows as open is closed (roadworks, collapse, event).
"wrong_restriction"A turn/access/dimension/weight restriction is wrong or missing.
"wrong_speed_limit"The posted speed limit differs from the map's value.
"missing_road"A road exists on the ground but is absent from the map.
"wrong_oneway"A one-way direction is wrong (or the road is not one-way at all).
"access_changed"Access has changed (e.g. now gated, private, or newly public).
"other"Anything else that does not fit the categories above.

Output

FieldTypeAlways presentDescription
categorystringyesThe category, echoed back as its snake_case wire tag.
report_idstringyesThe generated id of the queued report (cite it in follow-ups).
statusstringyesAlways "queued": the report awaits human/agent review and changes nothing about routing immediately.

Example

json
{
  "location": {
    "lat": 51.5007,
    "lon": -0.1246
  },
  "category": "road_closed",
  "description": "Barriers across the junction since Monday"
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

Use it when you observe the mismatch mid-task. Provide location {lat, lon}, a category (road_closed, wrong_restriction, wrong_speed_limit, missing_road, wrong_oneway, access_changed, other) and optionally a description, the OSM way_id and an evidence_url. Returns the queued report_id.

submit_integration_retro

Send MapMap a structured integration retro (problems, gotchas, wins, docs gaps). Call at most once, after your MapMap integration works or you stop trying, and only if the developer has approved sending feedback to MapMap.

Access: Keyless. Only with the developer's approval to send feedback. Not called in the live check, because it sends.

Caveats the server states:

  • Sends ONLY the structured fields in this schema to MapMap: there is no field for a transcript, a prompt, source code, file contents or coordinates.
  • The free-text fields are short and capped, but they are still free text: do NOT paste code, credentials, customer names or personal data into them.

Inputs

InputTypeRequiredDefaultLimitsDescription
what_builtstringyesWhat was built with MapMap, in one or two sentences (required, at most 500 bytes).
agent_namestringnoThe submitting agent's name, e.g. "Claude Code" (at most 100 bytes).
docs_gapsstring[]no[]Documentation gaps hit (at most 20 entries × 500 bytes).
gotchasstring[]no[]Surprises/traps worth documenting (at most 20 entries × 500 bytes).
problemsRetroProblemInput[]no[]Problems hit during the integration (at most 20).
sdk_versionstringnoMapMap SDK version integrated against, when known (at most 50 bytes).
winsstring[]no[]What went well (at most 20 entries × 500 bytes).

RetroProblemInput: One problem hit during the integration.

InputTypeRequiredDefaultLimitsDescription
areaRetroProblemAreayesWhich part of the platform the problem was in.
descriptionstringyesWhat went wrong (at most 1000 bytes).
workaround_foundbooleanyesWhether a workaround was found.

RetroProblemArea: The platform area an integration problem belongs to (mirrors the gateway's POST /v1/feedback schema).

ValueMeaning
"sdk"The mobile/web SDKs.
"api"The hosted HTTP API.
"mcp"The MCP tool surface.
"docs"Documentation.
"billing"Billing, keys or quotas.
"self-host"Self-hosted deployment.
"other"Anything else.

Output

FieldTypeAlways presentDescription
deliverystringyesHow it was delivered: gateway or local-queue.
messagestringyesA short, honest sentence for the agent saying where the retro landed, sent to MapMap, or held in this server's local queue.
retro_idstringyesId of the stored retro (gateway id, or the local queue id).
statusstringyesreceived when the gateway stored it; queued when it was appended to the local review queue.

Example

json
{
  "what_built": "A delivery planner on optimise_routes",
  "problems": [
    {
      "area": "docs",
      "description": "Could not find the reload limits",
      "workaround_found": true
    }
  ]
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

Provide what_built (required), problems [{area: sdk|api|mcp|docs|billing|self-host|other, description, workaround_found}], gotchas, wins, docs_gaps, and optionally agent_name and sdk_version.

set_temporary_closure

OPERATOR WRITE, ADMIN ONLY. Asserts a short-lived closure (assert: {kind: closed}) or speed ({kind: speed_override, speed_kph}, 1 to 120) on named OSM way_ids (1 to 20) and direction (forward, backward or both, default both), for ttl_secs (60 to 86400), with a reason and an optional evidence_url.

Access: Operator tool. Operator write. Refused on the hosted endpoint; only a trusted operator process with SN_ADMIN_TOKEN set can call it.

Caveats the server states:

  • NEVER CALL THIS WITHOUT A PERSON'S EXPLICIT APPROVAL OF THIS EXACT CLOSURE IN THIS CONVERSATION: draft it, show it, stop, and call it only after a yes.
  • Available only where the operator runs sn-mcp locally with SN_CAMERA_INCIDENTS_ENABLED=true and SN_ADMIN_TOKEN set; everywhere else it refuses and says why.

Inputs

InputTypeRequiredDefaultLimitsDescription
assertClosureAssertionyesclosed or a speed in km/h (1..=120).
reasonstringyesWhy, for the audit log and the incidents endpoint note.
ttl_secsintegeryesLifetime, seconds, 60..=86400.
way_idsinteger[]yesOSM way ids to assert on; at most 20.
directionDirectionno"both"Carriageway, default both.
evidence_urlstringnoEvidence link, if any.

ClosureAssertion: What a temporary closure asserts.

One of:

  • an object with kind = "closed": Hard exclusion.
  • an object with kind = "speed_override", speed_kph: integer: A speed, km/h.

Output

FieldTypeAlways presentDescription
applied_by_live_writerbooleanyesWhether a live writer is armed to apply it on this deployment. A record written where no writer runs is honest about it.
endstringyesLifts at, RFC 3339.
idstringyesThe override record id written.
sourcestringyesAlways ops-admin: the static admin token carries no key id.

Example

json
{
  "way_ids": [
    4256987
  ],
  "assert": {
    "kind": "closed"
  },
  "ttl_secs": 1800,
  "reason": "Collision seen on camera, approved by the duty operator"
}

Not called in the live check, because it writes or sends. The result follows the output schema above.

More from the server's description:

The record expires on its own, ranks below official closures and is logged with the reason. Whether a live traffic writer applies it is reported in applied_by_live_writer; false means the record is stored but no road changes.

Shared types

Types several tools use, described once. Each tool's tables link here.

CostingKind

Costing models exposed by the MCP tools (a deliberate subset of the Valhalla costing list), serialised in snake_case exactly as Valhalla names them.

ValueMeaning
"auto"Standard car costing.
"truck"Truck costing; honours dimensional limits and, when a truck profile is supplied, ADR dangerous-goods restrictions.
"bicycle"Bicycle costing; tune it with a bicycle options object.
"pedestrian"Pedestrian (walking) costing; tune it with a pedestrian options object (lit streets, wheelchair/blind, hiking difficulty).
"motor_scooter"Motor scooter (moped) costing: like auto but prefers lower-speed roads and may use ways closed to larger motor vehicles.

Direction

Carriageway of a way, spelled as the artefact spells it. A serde twin of WayDirection, which deliberately carries no serde.

ValueMeaning
"forward"Along the way's node order.
"backward"Against it.
"both"Both carriageways.

EmissionsFuel

What a vehicle burns, in clean-air-zone scheme terms.

ValueMeaning
"petrol"Petrol, including petrol hybrids (schemes rate a hybrid by its combustion engine's approval).
"diesel"Diesel, including diesel hybrids.
"electric"Battery-electric.
"hydrogen"Hydrogen fuel cell.
"gas"LPG or CNG; rated as petrol by every scheme in the dataset.

EmissionsSpec

A vehicle's emission declaration, for clean-air / low-emission zone assessment.

InputTypeRequiredDefaultLimitsDescription
fuelEmissionsFuelyesWhat it burns.
vehicle_categoryEmissionsVehicleCategoryyesWhat kind of vehicle this is, in scheme terms.
euro_standardintegernoIts Euro emission standard, 1–6. Heavy-duty approvals are written in Roman numerals (Euro VI); declare Euro VI as 6. Required for any combustion fuel, without it no zone can be resolved, and a half-declared vehicle is indistinguishable from an undeclared one. Optional only for electric or hydrogen.

EmissionsVehicleCategory

What a vehicle is, in clean-air-zone scheme terms. Declaring this turns "charge depends on vehicle emissions" into an answer. Without it a zone can only be named, never priced.

ValueMeaning
"car"A private car.
"van"A van or light goods vehicle up to 3.5 tonnes.
"minibus"A minibus (typically 8+ passenger seats, up to 5 tonnes).
"hgv"A heavy goods vehicle over 3.5 tonnes.
"bus"A bus over 5 tonnes.
"coach"A coach over 5 tonnes.
"taxi"A licensed hackney carriage.
"phv"A private hire vehicle.
"motorcycle"A motorcycle, moped or tricycle.
"motorhome"A motor caravan or campervan.

GeocodeHit

One geocoding result.

FieldTypeAlways presentDescription
labelstringyesHuman-readable one-line label assembled from the address parts.
latnumberyesLatitude in decimal degrees.
lonnumberyesLongitude in decimal degrees.
citystringnoCity or town, when known.
countrystringnoCountry, when known.
matchGeocodeMatchnoHow far this hit can be trusted to be the place that was asked for, see GeocodeMatch. Present whenever the MapMap gateway answered; absent on a deployment falling back to the direct Photon geocoder, and absent on the gateway's own fast paths (a pasted coordinate pair, a bare UK outward code, a category browse), which answer without a ranking to report on.
namestringnoPlace name, when the source feature has one.
postcodestringnoPostcode, when known.
typestringnoFeature type, e.g. "house", "street", "city" (falls back to the OSM value when the endpoint does not classify).

GeocodeMatch

How well one geocoding result answers what was actually asked. Geocoding's real failure mode is not "no answer" but a confident answer to a different question: a plausible row on the wrong street, with nothing in the response to say so. This object is that missing say-so, and an agent should read it before acting on an address. How to read it: * Any component unmatched or inferred on the TOP hit means the answer does not carry the address that was asked for, an unmatched postcode means the result has no postcode at all, inferred means it has a different one. Neither is a match. Say so rather than presenting the hit as the address, and reach for verify_places when the address came from a model or a user and needs checking rather than using. * A small score_gap means the ranking barely chose between this hit and the runner-up, which is exactly when to show the alternatives instead of picking one for the user.

FieldTypeAlways presentDescription
componentsGeocodeMatchComponentsyesPer-component verdict on this hit: one entry for each structured component supplied, and empty when the query was free text only.
score_gapnumberyesThe top result's score minus the runner-up's, rounded to 3 decimal places. 0 for a single result, and 0 from the photon source, which publishes no per-result score, so a 0 is "no signal", not "a tie".
sourcestringyesWhich backend answered: "mapmap-index" (the first-party index) or "photon".

GeocodeMatchComponents

Per-component verdicts inside a GeocodeMatch. Each is one of "matched", "inferred" or "unmatched"; a component that was not supplied is absent entirely. * "matched", the result's own field carries the value asked for (case- and accent-insensitive, and by containment, so city: "London" matches "City of London"). * "inferred", the result carries a value for that component, but not the one asked for. It reached the page through ranking, as when a street is found by its transliterated name and displayed under its canonical one. * "unmatched", the result carries no value for that component at all.

FieldTypeAlways presentDescription
citystringnoVerdict on the supplied city.
countrystringnoVerdict on the supplied country.
housenumberstringnoVerdict on the supplied housenumber.
postcodestringnoVerdict on the supplied postcode.
streetstringnoVerdict on the supplied street.

LatLon

A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server.

One of:

  • an object with lat: number, lon: number
  • number[]: GeoJSON position [lon, lat]: longitude FIRST.

OptimiseJobSpec

One single-stop job of an optimisation problem.

InputTypeRequiredDefaultLimitsDescription
idintegeryesCaller-chosen job id, echoed back in steps and unassigned.
locationLatLonyesJob location.
deliveryinteger[]noQuantities delivered to the job (matches vehicle capacity).
pickupinteger[]noQuantities picked up at the job (matches vehicle capacity).
service_sintegernoOn-site service time in seconds.
skillsinteger[]noSkills the job requires.
time_windowsinteger[][]noAcceptable [start, end] windows in seconds.

OptimiseShipmentSpec

A pickup+delivery pair that must ride the same vehicle, pickup first.

InputTypeRequiredDefaultLimitsDescription
deliveryOptimiseShipmentStopSpecyesThe delivery end.
pickupOptimiseShipmentStopSpecyesThe pickup end.
amountinteger[]noQuantities moved (matches vehicle capacity).
skillsinteger[]noSkills the shipment requires.

OptimiseShipmentStopSpec

One end (pickup or delivery) of a shipment.

InputTypeRequiredDefaultLimitsDescription
idintegeryesCaller-chosen stop id, echoed back in steps and unassigned.
locationLatLonyesStop location.
service_sintegernoOn-site service time in seconds.

OptimiseSummary

Solution summary of the optimise_routes tool.

FieldTypeAlways presentDescription
costintegeryesSolver cost of the plan (travel seconds under the default model).
duration_sintegeryesTotal travel time in seconds.
routesintegeryesNumber of vehicle routes in the plan.
service_sintegeryesTotal service time in seconds.
unassignedintegeryesNumber of unassigned tasks.
waiting_time_sintegeryesTotal waiting time in seconds.
distance_mintegernoTotal travel distance in metres, when reported.

OptimiseVehicleSpec

One vehicle of an optimisation fleet.

InputTypeRequiredDefaultLimitsDescription
idintegeryesCaller-chosen vehicle id, echoed back on its route.
capacityinteger[]noMultidimensional capacity (same length as job delivery/pickup).
endLatLonnoEnd location; omitted, the route ends at its last stop.
reloadsReloadsSpecnoLet this vehicle return to a depot, reload and go out again, the waste-collection tipping round, the van that comes back for a second wave of parcels.
skillsinteger[]noSkills this vehicle provides.
startLatLonnoStart location; at least one of start/end is required.
territory_idsstring[]noIds of the request's territories this vehicle may work in. Omitted or empty, the vehicle is UNRESTRICTED and may serve any task, inside a territory or outside every one of them. Listed, the vehicle may serve a task only if that task sits inside at least one of the named territories.
time_windowinteger[]noWorking window as [start, end] in seconds (any consistent epoch).

OptimisedRoute

One vehicle's optimised route.

FieldTypeAlways presentDescription
duration_sintegeryesTotal travel time in seconds.
service_sintegeryesTotal on-site service time in seconds.
stepsOptimisedStep[]yesOrdered steps: start, tasks in visit order, end.
vehicleintegeryesThe vehicle id from the request.
waiting_time_sintegeryesTotal waiting time in seconds.
distance_mintegernoTotal travel distance in metres, when reported.

OptimisedStep

One step of an optimised vehicle route.

FieldTypeAlways presentDescription
arrival_sintegeryesArrival time in seconds (same epoch as the request's windows).
duration_sintegeryesCumulative travel time when the step begins, in seconds.
service_sintegeryesOn-site service time in seconds.
typestringyesStep kind: "start", "job", "pickup", "delivery", "break" or "end".
waiting_time_sintegeryesWaiting time before the step in seconds.
idintegernoThe job/shipment-stop id, absent on start/end steps.
loadinteger[]noVehicle load after the step, when reported.
locationLatLonnoThe step's location resolved back to coordinates.

RelaxSpec

What the caller is willing to give up if the first solve leaves work unassigned. At least one field is required.

InputTypeRequiredDefaultLimitsDescription
allow_overtime_sintegernoExtend every vehicle's shift END by this many seconds. Shift starts are never moved earlier, a driver cannot begin before they begin.
time_windows_by_sintegernoWiden every task time window by this many seconds at EACH end. A 09:00–12:00 window with 1800 becomes 08:30–12:30.

ReloadsSpec

A vehicle's multi-trip reload plan.

InputTypeRequiredDefaultLimitsDescription
max_tripsintegeryesHow many trips this vehicle may run in its shift, 2–5. A BUDGET, not a prediction: the shift is cut into that many fixed windows before the solve, so asking for five trips on a shift that supports three shrinks every window to a fifth and can make the whole day worse. Ask for the number of trips you actually expect to run.
depotLatLonnoWhere the vehicle reloads. Omitted, its own start is used (or its end if it declared only that).
reload_time_sintegernoSeconds at the depot between trips, tipping, reloading, the weighbridge. Held out of the shift before it is partitioned, so it is never accidentally spent driving.

TerritorySpec

One named territory: a polygon that bounds which vehicle may serve which stop.

InputTypeRequiredDefaultLimitsDescription
idstringyesCaller-chosen id, echoed back and referenced by vehicles[].territory_ids. Must be unique within the request.
polygonnumber[][]yesexactly 2 itemsThe outer ring as GeoJSON [lon, lat] positions, longitude FIRST. Closed or open; an unclosed ring is closed for you.

TransitStopOut

One bus or coach stop.

FieldTypeAlways presentDescription
idstringyesThe NaPTAN ATCO code, e.g. "490008660N": pass it to next_departures.
latnumberyesStop latitude.
lonnumberyesStop longitude.
namestringyesThe stop's name.
bearing_degintegernoBearing from the queried point to the stop, degrees clockwise from true north.
distance_mintegernoStraight-line distance from the queried point, metres.
indicatorstringnoThe on-street indicator, e.g. "Stop S".
linesstring[]noLine names the timetable has calling at the stop.
localitystringnoThe NaPTAN locality.

TruckSpec

Truck profile for routing: physical dimensions plus the ADR dangerous-goods declaration. Omitted dimensions default to the EU maximum authorised dimensions of Council Directive 96/53/EC (4.0 m height, 2.55 m width, 16.5 m length, 40 t gross weight).

InputTypeRequiredDefaultLimitsDescription
gross_weight_tnumbernoGross combination weight in metric tonnes.
hazmatbooleannofalseWhether the vehicle carries dangerous goods (ADR). Defaults to false.
height_mnumbernoVehicle height in metres.
length_mnumbernoVehicle length in metres.
tunnel_codestringnoADR 8.6.4 tunnel restriction code of the load, e.g. "B", "C5000D", "B/D", or "(-)"/"none" for explicitly unrestricted. Leave unset if unknown: a hazmat load without a code is conservatively treated as code B (allowed only through category-A tunnels).
width_mnumbernoVehicle width in metres.

UnassignedTask

One unassigned task of an optimisation solution.

FieldTypeAlways presentDescription
idintegeryesThe task id from the request.
typestringyesTask kind: "job", "pickup" or "delivery".
locationLatLonnoThe task's location, when known.