Documentation menu
Analysis APIs: isochrones, matrices, along-route search, map matching, safety cameras, incidents & weather
Eight endpoints answer the fleet questions that sit around routing rather
than on it: where can this vehicle get to in 30 minutes?
(POST /isochrone), what are the travel times between every depot and
every stop? (POST /matrix), what can I stop at on the way, and what
does the detour really cost? (POST /route/along), which roads
did this GPS trace actually drive? (POST /trace_route,
POST /trace_attributes), which safety cameras sit on this
route? (POST /v1/cameras/along), which closures or lane
restrictions sit on this route? (POST /v1/incidents/along), and
what will the weather be doing when I actually get there?
(POST /v1/weather/along). The first five
accept the same costing models as POST /route, including truck costing
with dimensional and ADR constraints (ADR is the European agreement on
carriage of dangerous goods by road; tunnel codes B–E restrict which
tunnels a hazmat load may use; see conventions for
the full table). An isochrone for a 44-tonne hazmat artic is the area that
lorry can legally reach, not the area a car could.
Availability
Status, honestly: the hosted gateway at https://api.mapmap.ai is live:
sign up for a key, or run the self-host distro.
Base URL and authentication conventions are on the
conventions page.
Shared conventions
- You need an API key (
Authorization: Bearer snk_…; get one in the quickstart). - Locations everywhere on this page are
{ "lat": …, "lon": … }objects with named keys, so there is no coordinate-order ambiguity. costingis one ofauto,truck,bus,motor_scooter,motorcycle,bicycle,pedestrian.costing_optionsis passed to the engine verbatim, keyed by costing name. For trucks:{"truck": {"height": 4.0, "width": 2.55, "length": 16.5, "weight": 40.0, "axle_load": 9.0, "axle_count": 5, "hazmat": true, "top_speed": 90.0, "adr_tunnel_code": "C"}}; dimensions in metres, weights in tonnes, speed in km/h.adr_tunnel_codeis honoured by our ADR-extended engine; a stock engine ignores unknown keys, so requests stay portable.- Requests bill by size: a matrix bills one call per started block of 25
elements (
sources × targets), capped at 10,000 elements per request; an isochrone bills one call per contour (max 10 per request); an along-route search bills like a matrix on its detour budget (4 × max_detourselements, max 25 detours), plus one call when it computes the route for you. The class is Standard normally, Premium when the body carries"costing": "truck". See pricing for the per-class rates.
Isochrones: POST /isochrone
Reachability contours from an origin: how far can you get within a time or distance budget. Use it for delivery-coverage maps, depot placement, "can we serve this postcode in under 45 minutes?" checks, and driver-hours planning. The response is GeoJSON you can drop straight onto a map.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
locations | array of {lat, lon} | yes (≥ 1) | Origin(s) the reachability is computed from. |
costing | string | yes | Costing model (see shared conventions). |
contours | array | yes (≥ 1) | Each contour is a time in minutes or a distance in kilometres; a contour with neither is a 400. |
polygons | boolean | no | Return polygons instead of the default linestrings. |
denoise | number | no | 0–1; higher drops smaller contour islands. |
generalize | number | no | Geometry simplification tolerance in metres. |
show_locations | boolean | no | Include the input locations as GeoJSON points in the response. |
costing_options | object | no | Verbatim costing options, e.g. {"truck": {…}}. |
id | string | no | Opaque identifier echoed back. |
Example
Where can a 4-metre-high truck get to from a London depot in 10 and 20 minutes:
export BASE=https://api.mapmap.ai
export API_KEY=snk_...
curl -fsS -X POST "$BASE/isochrone" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [{ "lat": 51.5074, "lon": -0.1278 }],
"costing": "truck",
"contours": [{ "time": 10 }, { "time": 20 }],
"polygons": true,
"costing_options": { "truck": { "height": 4.0 } }
}'
Response (truncated)
A GeoJSON FeatureCollection, one feature per contour, returned from the
routing engine. Each feature's properties carry the contour value (in
the unit you asked for: minutes or km) and a metric of "time" or
"distance":
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "contour": 10.0, "metric": "time" },
"geometry": {
"type": "Polygon",
"coordinates": [[[-0.14, 51.50], [-0.11, 51.50], [-0.11, 51.52], [-0.14, 51.50]]]
}
}
]
}
(GeoJSON geometry itself is [lon, lat]; that is the GeoJSON standard,
not ours.)
Deployment note: on self-hosted deployments running the GraphHopper
engine (SN_ROUTING_ENGINE=graphhopper), isochrones are single-origin,
cannot mix time and distance contours in one call, and the rings match
your contours exactly only when they are evenly spaced. The Valhalla
engine (the default) has none of these limits.
Matrix: POST /matrix
A many-to-many travel-time and distance matrix: sources are the rows,
targets the columns. This is the workhorse behind dispatch decisions
("which of my 12 drivers is genuinely closest, by road, in a lorry?") and
the same computation POST /optimise runs
internally. Call it directly when you want the raw numbers for your own
solver or ranking logic.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sources | array of {lat, lon} | yes (≥ 1) | Row locations (origins). |
targets | array of {lat, lon} | yes (≥ 1) | Column locations (destinations). |
costing | string | yes | Costing model (see shared conventions). |
costing_options | object | no | Verbatim costing options, e.g. {"truck": {…}}. |
units | string | no | kilometers or miles; accepted, but the gateway normalises response distances to metres regardless. |
id | string | no | Opaque identifier echoed back. |
Example
One depot to two delivery cities, truck costing:
export BASE=https://api.mapmap.ai
export API_KEY=snk_...
curl -fsS -X POST "$BASE/matrix" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sources": [{ "lat": 51.5074, "lon": -0.1278 }],
"targets": [
{ "lat": 52.4862, "lon": -1.8904 },
{ "lat": 53.4808, "lon": -2.2426 }
],
"costing": "truck",
"costing_options": { "truck": { "height": 4.0, "weight": 40.0 } }
}'
Response
Normalised and complete: this is the whole shape. Row-major matrices:
durations in seconds, distances in metres, null where a pair
is unreachable under the chosen costing:
{
"durations": [[9000.0, 12500.0]],
"distances": [[171100.0, 262000.0]]
}
durations[i][j] is from sources[i] to targets[j]. Unlike
/optimise, unreachable pairs here are honest
nulls, not sentinel costs.
Along-route search: POST /route/along
"Where can my driver get diesel on the way, and what does the stop
really cost?" This endpoint searches your own places (the dataset
you uploaded to /places) along a route, and
ranks every candidate by honest detour cost: for each place inside the
corridor, the routing engine measures leave-route → place → rejoin-route
against staying on the route, so detour_seconds is real added driving
time under your costing (a truck's detour, not a car's), never
straight-line optimism. One primitive covers diesel, truck parking and any
consumer POI: "coffee, adds 4 min".
Give it either a full route request (route, the same body as
POST /route; the gateway computes the route for you) or an existing
geometry (shape, polyline6 from a /route response leg). A cheap
corridor pre-filter runs server-side first, so only the max_detours
nearest candidates ever reach the matrix engine.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
route | object | one of route / shape | A Valhalla-style route request (same body as POST /route, including the truck/ADR options). Detour matrices reuse its costing. |
shape | string | one of route / shape | An existing route geometry as an encoded polyline with six digits of precision (polyline6). |
costing | string | no | shape mode only (default auto): costing model for the detour measurements. |
costing_options | object | no | shape mode only: verbatim costing options, e.g. {"truck": {…}}. |
category | string | no | Only places carrying this category (case-insensitive), e.g. "diesel", "truck_parking", "coffee". |
corridor_m | number | no | Corridor half-width in metres, 50–10,000 (default 2,000). |
max_detours | integer | no | Candidates ranked by detour cost, 1–25 (default 10); also the billing budget. |
Example
Truck parking within 5 km of a Dover → Birmingham truck route:
export BASE=https://api.mapmap.ai
export API_KEY=snk_...
curl -fsS -X POST "$BASE/route/along" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"route": {
"locations": [
{ "lat": 51.1279, "lon": 1.3134 },
{ "lat": 52.4862, "lon": -1.8904 }
],
"costing": "truck",
"costing_options": { "truck": { "height": 4.0, "weight": 40.0 } }
},
"category": "truck_parking",
"corridor_m": 5000,
"max_detours": 10
}'
Response
Hits ranked by detour_seconds, cheapest stop first. along_fraction is
how far along the route the stop sits (0 = origin, 1 = destination);
offset_m its straight-line distance from the route; unreachable counts
in-corridor candidates the engine could not connect:
{
"count": 2,
"corridor_m": 5000.0,
"route_duration_s": 10620.0,
"route_distance_m": 273400.0,
"unreachable": 0,
"results": [
{
"place": {
"id": "tp-ashford", "name": "Ashford Truckstop",
"lat": 51.13, "lon": 0.85, "categories": ["truck_parking"]
},
"detour_seconds": 240.0,
"detour_km": 2.6,
"along_fraction": 0.18,
"offset_m": 850.0
},
{
"place": {
"id": "tp-oxford", "name": "Oxford Services",
"lat": 51.83, "lon": -1.19, "categories": ["truck_parking"]
},
"detour_seconds": 660.0,
"detour_km": 8.9,
"along_fraction": 0.71,
"offset_m": 2300.0
}
]
}
No dataset uploaded yet (or nothing inside the corridor) is a
well-formed empty result (count: 0), not an error. A 404 means the
deployment has no customer-places storage configured (SN_PLACES_DIR on
self-host; always on for the hosted gateway). Agents get the same
capability as the MCP server's search_along_route tool.
Map matching: POST /trace_route and POST /trace_attributes
Both snap a recorded GPS trace to the road network. /trace_route returns
a turn-by-turn route, the same response shape as POST /route (see the
API reference), for cleaning up noisy telematics
tracks into displayable routes. /trace_attributes returns the matched
road segments and their attributes instead, for questions like "did this
vehicle use the motorway?" or mileage auditing by road actually driven.
Both take the same request body:
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
shape | array of {lat, lon} | one of shape / encoded_polyline | The GPS trace as an ordered list of points. |
encoded_polyline | string | one of shape / encoded_polyline | The trace as an encoded polyline with six digits of precision (polyline6). |
costing | string | yes | Costing model (see shared conventions). |
shape_match | string | no | edge_walk (exact edge sequence, no snapping), map_snap (snap each point to the most likely edge), or walk_or_snap (try the first, fall back to the second; the default). |
filters | object | no | /trace_attributes only: which edge attributes to include or exclude. |
units | string | no | kilometers (default) or miles, for response length fields. |
costing_options | object | no | Verbatim costing options. |
id | string | no | Opaque identifier echoed back. |
Example
export BASE=https://api.mapmap.ai
export API_KEY=snk_...
curl -fsS -X POST "$BASE/trace_route" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shape": [
{ "lat": 51.5074, "lon": -0.1278 },
{ "lat": 51.5090, "lon": -0.1300 }
],
"costing": "auto",
"shape_match": "map_snap"
}'
Response (truncated)
/trace_route returns the standard route response: durations in seconds,
length in the trip's units (kilometres by default), leg shape as
polyline6:
{
"trip": {
"locations": [
{ "lat": 51.5074, "lon": -0.1278 },
{ "lat": 51.5090, "lon": -0.1300 }
],
"legs": [{
"maneuvers": [
{ "type": 1, "instruction": "Drive north on High Street.",
"time": 240.0, "length": 0.4,
"begin_shape_index": 0, "end_shape_index": 2 }
],
"summary": { "time": 240.0, "length": 0.4 },
"shape": "}~ycbBhavgN..."
}],
"summary": { "time": 240.0, "length": 0.4 },
"status": 0,
"status_message": "Found route between points",
"units": "kilometers",
"language": "en-US"
}
}
/trace_attributes with the same body returns an open JSON document from
the engine: the matched geometry plus per-edge attributes. The exact
attribute set depends on the engine and your filters; expect shape
(polyline6), matched_points and an edges array:
{
"shape": "}~ycbBhavgN...",
"matched_points": [
{ "lat": 51.5074, "lon": -0.1278 },
{ "lat": 51.5090, "lon": -0.1300 }
],
"edges": [
{ "way_id": 12345, "speed": 48 }
],
"units": "kilometers"
}
A request with neither a non-empty shape nor an encoded_polyline is a
400.
Safety cameras along a route (POST /v1/cameras/along)
Given a route shape, returns the safety cameras on it: where each camera sits along the route, its type, and the enforced limit where known. Built for navigation clients that warn the driver on approach. The data is baked per territory at build time, and exact positions are served only where that is lawful (see the jurisdiction policy below).
Two shapes of record come back. Point devices are a single camera at
a position. Corridors are a stretch of enforced road, either
average-speed section control or one of Ireland's published mobile-camera
zones; they carry a zone object and the arc offsets of both ends. A
corridor is always one entry, never one per device.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
shape | string | yes | The route geometry as an encoded polyline with six digits of precision (polyline6), exactly as POST /route responses emit their leg shape. |
buffer_m | number | no | Corridor half-width in metres for matching cameras to the shape. Default 40; values above 100 are clamped to 100; zero, negative or non-numeric is a 400. |
Example
export BASE=https://api.mapmap.ai
export API_KEY=snk_...
curl -fsS -X POST "$BASE/v1/cameras/along" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shape": "}~ycbBhavgN...",
"buffer_m": 40
}'
Response
{
"cameras": [
{ "arc_m": 1234.5, "lat": 51.5074, "lon": -0.1278,
"kind": "fixed", "limit_kph": 48, "bearing": null,
"limit_confidence": "osm",
"source": "osm", "licence": "ODbL-1.0" },
{ "arc_m": 4820.0, "lat": 53.3226, "lon": -6.4235,
"kind": "mobile_site", "limit_kph": null, "bearing": null,
"limit_confidence": "unknown",
"from_arc_m": 4820.0, "to_arc_m": 6320.0,
"zone": {
"start": [53.322565, -6.423538],
"end": [53.329367, -6.42377],
"length_km": 1.5,
"geometry": [[-6.423538, 53.322565], [-6.42377, 53.329367]],
"geometry_parts": [2]
},
"source": "ie-garda-current-zones", "licence": "CC-BY-4.0" }
],
"attribution": [
"Contains Irish Public Sector Data licensed under a Creative Commons Attribution 4.0 International (CC BY 4.0) licence."
],
"policy": { "country": "IE", "mode": "exact" }
}
camerasis ordered byarc_m, the distance in metres along the shape to each camera's on-route projection. For a corridor that is its start, so a client that only understands points needs no new code.kindisfixed,average(a section-control camera),redlight,mobile_site(a published mobile-enforcement zone) orunknown.limit_kphis the enforced limit where the data records one, on the camera itself or its enforcement zone.nullis common and honest rather than an error: Ireland's published zones carry no limit at all. Never substitute a guess.limit_confidencesays how farlimit_kphcan be trusted:official(from the enforcing authority),osm(from map tagging),inferred_from_road, orunknown; alwaysunknownwhenlimit_kphisnull.bearing, when present, is the camera facing in degrees clockwise from north. Cameras facing away from the direction of travel (more than 60 degrees off the local route bearing, typically the opposite carriageway) are filtered out server-side. Corridors carry no bearing: they are enforced in both directions.attributionlists the notices owed by the data actually returned. Display them verbatim. Empty when no cameras were returned.sourceandlicenceidentify where each record came from, so mixed responses stay auditable.
Corridor records
Corridor entries (average section control and mobile_site zones) add:
| Field | Type | Description |
|---|---|---|
zone.start / zone.end | [lat, lon] | The corridor's published ends. The direction carries no enforcement meaning. |
zone.length_km | number | Enforced length. Published where the authority states one, otherwise measured along the geometry. |
zone.geometry | [[lon, lat], …] | The corridor polyline, in GeoJSON coordinate order. |
zone.geometry_parts | [number, …] | Vertex count of each source part. Split geometry on these boundaries when drawing; joining the parts would draw connecting segments that are not in the data. |
from_arc_m / to_arc_m | number | Where the corridor starts and ends along this route, in metres. Use the interval between them, with zone.length_km and limit_kph, to run an average-speed calculation client-side. to_arc_m can be the smaller of the two when the route runs against the corridor's published direction. |
One corridor is one entry. Do not expect one entry per camera on a section-control stretch, and do not alert per device: that would warn the driver several times for a single enforcement event.
Jurisdiction policy
Camera-position warnings are illegal in some countries, so the gateway
applies a per-country policy server-side, before the response is built,
and reports what it did in policy:
mode | Behaviour |
|---|---|
exact | Exact camera positions are returned. |
omitted | The cameras array is empty; only the policy object is returned. |
The default is off: only countries on an explicit, legally reviewed
allowlist (currently the United Kingdom and Ireland) return exact.
Every other jurisdiction returns omitted, researched prohibitions
(France, Germany, Switzerland, Austria and others) and unresearched
countries alike. On routes that cross borders, each camera is also gated
on its own country, so a prohibited neighbour's cameras never appear.
Clients must not work around the policy with their own camera data.
Availability and billing
Bills as one Standard call, never Premium: it is a safety feature
and carries no extra charge. On deployments without camera data the
endpoint answers 501 (urn:sn-gateway:problem:cameras-not-enabled);
treat that as "feature not available here" and disable camera alerts,
rather than as an error. Self-hosters enable it by pointing
SN_CAMERAS_DIR at the per-territory cameras.json sidecars the
factory's safety-cameras stage produces.
Data sources and licences
Camera data is either OpenStreetMap-derived or published by the enforcing authority, and within one territory it is always one or the other, never a mixture. That separation is a licence requirement, not a preference: combining OSM and non-OSM records of the same feature type over the same area makes the whole dataset an ODbL derivative with share-alike.
| Territory | Source | Licence |
|---|---|---|
| Ireland | An Garda Síochána mobile safety camera zones (1,916 corridors) plus the published fixed and average-speed cameras (11 devices). OSM camera nodes are excluded. | CC BY 4.0 (Irish public sector information) |
| Everywhere else | OpenStreetMap | ODbL 1.0 |
The attribution array in every response carries the notices the
returned data obliges. Display them verbatim.
Ireland publishes no speed limits. Every Irish record has
limit_kph: null with limit_confidence: "unknown", and the API will
not infer one from the road: Ireland re-based its default rural limit to
60 km/h in February 2025, so an inference tuned to the old defaults would
be wrong across a large part of the network. Clients must not fill the
gap with a guess of their own either.
Traffic incidents along a route (POST /v1/incidents/along)
Given a route shape, returns the closures and lane restrictions on it:
where each one sits along the route, its cause and severity, and the
lane/time detail the source publishes. Built for the same "warn the
driver on approach" use case as /v1/cameras/along, over a different
data source: the National Highways Road and Lane Closures feed.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
shape | string | yes | The route geometry as a polyline6 string, exactly as POST /route responses emit their leg shape. |
buffer_m | number | no | Corridor half-width in metres. Default 60; values above 500 are clamped to 500; zero, negative or non-numeric is a 400. |
Example
curl -fsS -X POST "$BASE/v1/incidents/along" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "shape": "}~ycbBhavgN...", "buffer_m": 60 }'
Response
{
"incidents": [
{ "id": "7-1784655805-...-close", "arc_m": 0.0,
"from_arc_m": 0.0, "to_arc_m": 1326.1, "offset_m": 0.0,
"side": "unknown", "cause": "roadOrCarriagewayOrLaneManagement: laneClosures",
"severity": "lane_restriction", "closure_type": "laneClosures",
"road": "M1", "lanes_restricted": 1, "lanes_operational": 3,
"status": "active", "planned": false, "probable": false,
"overall_start": "2026-07-26T10:00:00Z", "overall_end": "2026-07-27T10:00:00Z",
"source": "uk-national-highways" }
],
"sources": [
{ "source_id": "uk-national-highways",
"network": "England strategic road network (motorways and trunk roads managed by National Highways)",
"coverage_note": "National Highways only. Local roads, and the separate Scotland/Wales trunk-road networks, are not covered.",
"licence": "OGL-UK-3.0" }
],
"attribution": ["Contains public sector information licensed under the Open Government Licence v3.0."]
}
incidentsis ordered byarc_m.from_arc_m/to_arc_mbound the extent of the closure's geometry that actually fell inside the corridor; for a closure spanning a long stretch of motorway this can be several kilometres; for one that only clips the corridor briefly,from_arc_mandto_arc_msit close together.severity(closure|lane_restriction|other) is derived only from the feed's own closure-type and lane-count fields, never a guess when those are absent.side(left|right|unknown) is geometric: which side of the route's own local bearing the incident sits on. The feed carries no carriageway-direction field, so this is not the same question as "does this affect my direction of travel".sourcesis always present, even with zero matching incidents. An emptyincidentsarray means "nothing on the covered network near this route", never "the road is clear". Readcoverage_notebefore trusting silence: National Highways covers the English strategic road network only, not local roads and not the separate Scotland/Wales networks.
Availability and billing
Bills as one Standard call, never Premium, the same posture as
/v1/cameras/along. On deployments without incident data the endpoint
answers 501 (urn:sn-gateway:problem:incidents-not-enabled);
treat that as "feature not available here", not an error. Self-hosters
enable it by pointing SN_INCIDENTS_DIR at a directory of per-source
sidecar files (see docs/API.md
for the sidecar shape). Licence: OGL v3.0 (UK Open Government Licence);
the attribution array carries the required notice verbatim.
Weather along a route (POST /v1/weather/along)
Samples a route and returns forecast weather for each point aligned to when you actually get there, not to now. A two-hour route departing at noon reads the forecast for the departure hour at its start and the "two hours from now" hour at its end; that time alignment is the whole point: "you hit the snow band near Shap at 14:00", not "it is snowing somewhere on this route right now".
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
shape | string | yes | The route geometry as a polyline6 string. |
depart_at | string | no | RFC 3339 departure time. Defaults to now. |
duration_s | number | no | Total route duration in seconds, used to compute each sample's ETA. Omit for a generic average-speed estimate (flagged duration_source: "estimated_default_speed" in the response); pass the real value from /route for accurate alignment. |
sample_interval_m | number | no | Sampling interval in metres. Default 25,000; clamped 5,000–100,000; capped at 40 samples total regardless of route length. |
Example
curl -fsS -X POST "$BASE/v1/weather/along" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "shape": "}~ycbBhavgN...", "depart_at": "2026-07-26T12:00:00Z", "duration_s": 7200 }'
Response
{
"samples": [
{ "lat": 54.4, "lon": -2.7, "arc_m": 180000.0,
"eta": "2026-07-26T14:00:00Z", "forecast_hour": "2026-07-26T14:00",
"available": true, "temperature_c": -1.2, "precipitation_mm": 0.6,
"rain_mm": 0.0, "snowfall_cm": 0.8, "precipitation_type": "snow",
"precipitation_probability_pct": 80.0,
"wind_speed_kph": 32.0, "wind_gusts_kph": 58.0,
"visibility_m": 3200.0, "weather_code": 71, "severe": false }
],
"depart_at": "2026-07-26T12:00:00Z", "duration_s": 7200.0,
"duration_source": "provided", "sample_interval_m": 25000.0,
"attribution": "Weather data by Open-Meteo.com (CC BY 4.0)"
}
wind_gusts_kphis the figure that matters for high-sided vehicles.severeis derived only from the source's WMO weather code (thunderstorm and heavy-precipitation codes), never a proprietary severe-weather product, and omitted (notfalse) when the sample itself is unavailable.- A sample whose upstream fetch failed, or whose ETA falls beyond the
source's forecast horizon, comes back
"available": falsewith anunavailable_reason; the request never fails outright because one sample couldn't be resolved.
Availability, billing and licence
Bills as one Standard call regardless of sample count. 501
(urn:sn-gateway:problem:weather-not-enabled) unless the operator sets
SN_OPEN_METEO_URL. Read this before enabling commercially:
Open-Meteo's free, keyless endpoint is non-commercial use only; a
commercial deployment needs an Open-Meteo paid plan or a self-hosted
instance (Open-Meteo is open source, AGPLv3). See
THIRD-PARTY-NOTICES.md
for the full licence position. The weather data itself is CC BY 4.0;
every response carrying data includes an attribution string.
Errors
Errors are application/problem+json (see
conventions for the envelope and the 402-vs-429
distinction). All eight endpoints share the same table:
| Status | type URN | Meaning | Retry? |
|---|---|---|---|
| 400 | urn:sn-gateway:problem:bad-request | Malformed JSON; empty locations, contours, sources or targets; a contour with neither time nor distance; a trace request with no trace; an along-route request with neither/both of route and shape, or an out-of-range corridor_m/max_detours; a cameras or incidents request with an undecodable shape or a non-positive buffer_m; a weather request with a non-RFC-3339 depart_at or a non-positive duration_s/sample_interval_m. | No; fix the request. |
| 404 | urn:sn-gateway:problem:not-found | /route/along only: customer places are not configured on this deployment. | No. |
| 501 | urn:sn-gateway:problem:cameras-not-enabled / …:incidents-not-enabled / …:weather-not-enabled | The corresponding endpoint's data source is not configured on this deployment. | No. Feature-detect and disable that feature. |
| 401 | urn:sn-gateway:problem:unauthorized | Missing or invalid API key. | No. |
| 429 | urn:sn-gateway:problem:quota-exceeded or …:rate-limited | Monthly quota or rate limit; rate-limited bodies carry retry_after (seconds). | After retry_after. |
| 502 | urn:sn-gateway:problem:upstream-error | The routing engine failed or returned garbage. | Yes; transient. |
Note that /v1/weather/along never returns a 502 for a per-sample
upstream failure: it degrades that sample to
"available": false inside a normal 200 response instead (see above).
Next steps
- Route optimisation: the solver that consumes these matrices, with the same truck/ADR constraints
- API reference: the full endpoint surface, including
POST /route(whose response/trace_routeshares) and the/placesAPI that feeds along-route search - Conventions: base URL, auth, error envelope, ADR tunnel codes
- MCP server: the
matrixandsearch_along_routetools expose these capabilities to agents