curl -X POST 'https://api.cyclemate.club/explain-supersafe/' \
-H 'apikey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"origin": [-0.0754, 51.5465],
"destination": [-0.0605, 51.5265],
"nighttime": true
}'
{
"summary": "Takes the C13 cycleway and avoids moderately busy Goldsmiths' Row. Most of the ride is on cycleways and quiet streets, for about a minute more.",
"identical": false
}
{
"summary": "The most direct route as well as the quietest",
"identical": true
}
{
"summary": null,
"identical": false,
"no_route": true
}
Routing
Explain a route
A short explanation of how the SuperSafe cycling route differs from the most direct route
curl -X POST 'https://api.cyclemate.club/explain-supersafe/' \
-H 'apikey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"origin": [-0.0754, 51.5465],
"destination": [-0.0605, 51.5265],
"nighttime": true
}'
{
"summary": "Takes the C13 cycleway and avoids moderately busy Goldsmiths' Row. Most of the ride is on cycleways and quiet streets, for about a minute more.",
"identical": false
}
{
"summary": "The most direct route as well as the quietest",
"identical": true
}
{
"summary": null,
"identical": false,
"no_route": true
}
Overview
Compares the SuperSafe cycling route between two points against the most direct cycling route and returns a short explanation of the difference, for example"Takes the C13 cycleway and avoids moderately busy Goldsmiths' Row. Most of the ride is on cycleways and quiet streets, for about a minute more.".
Use it to caption a SuperSafe route in your own UI. Call it alongside POST /multi-modal/v2/ rather than before it: the explanation is a label for a route you are already showing, and it takes 1 to 2 seconds longer to produce than the route itself. Pass the route pair back as context so the explanation can cover everything the response already told you about it: how much of each route is on cycleways, quiet streets and main roads, how well lit each is at night, the climbing, the time difference, and what the SuperSafe route steered around.
The explanation is generated text, two or three sentences, capped at 320 characters, in English. It is not deterministic: the same request can produce different wording on different calls. Display it, do not parse it, and do not use it as a cache key.
SuperSafe routing is available in supported cities (NYC, LA, SF Bay Area, Chicago, London).
Authentication
This endpoint requires an API key. Include it in theapikey header:
apikey: YOUR_API_KEY
Rate limits
Each call runs two routes and generates text, so this endpoint is limited more tightly than the routing endpoints: 10 requests per minute for API keys, 20 requests per minute for user sessions. On429, honor the Retry-After header.
What the explanation can say
The first sentence names the single most important difference: a road closure or roadworks the route detours around when one applies at the departure time, otherwise the busiest street it avoids and what it takes instead. It names at most one street per side there, only mentions streets that appear on one of the two routes, and names a signed cycle route reference (C13, CS7) in preference to the underlying street.
The later sentences, present only when context gives them something to say, describe the whole route: the share of the ride on cycleways and quiet streets, lighting on nighttime comparisons, a restricted turn or rider-reported street it avoids, and climbing. Time is mentioned only when the SuperSafe route is at least a minute longer, in whole minutes; the explanation never claims the two routes take the same time, and never states percentages or distances.
Request body
array
required
Start point as
[longitude, latitude].array
required
End point as
[longitude, latitude].Optional parameters
Optional parameters
boolean
default:"false"
Compare the routes on the nighttime cost tier, which prefers lit streets. Pass the
metadata.nighttime value from the routes you are showing so the sentence describes the same pair of routes.string
Planned departure as
"YYYY-MM-DDTHH:mm" with no timezone offset, interpreted in the route city’s timezone. Determines which temporary road avoidances apply to the comparison. Pass the same value you routed with. Omit to compare for now. Unparseable values return 400.object
The pair of routes you are showing, summarised from the
POST /multi-modal/v2/ response. Without it the explanation only covers the streets where the two routes differ. A malformed value returns 400 naming the field.safe(object, required) anddirect(object ornull): one summary per route,directbeingnullwhen the response collapsed the two onto one route. Each carrieslabel(the option’slabel),meters,seconds,climb_metersanddescent_meters(the leg totals),character_meters(metres of the cycling legs on eachsafety_classification:cycleway,quiet,shared,moderate,busy) andlit_share(the fraction of the cycling distance on lit steps, 0 to 1;nullon daytime comparisons).avoided(array, required): the SuperSafe option’savoidanceentries in plain words, up to 12. Each haskind(busy_street,restricted_turn,roadworks,closure_windoworrider_report) andname(the street, ornull), plus per kind:metersfor a busy street;ontofor a restricted turn;schedule(the window in words, for example"Saturdays 09:00–17:00"),attribution(who reported it) andseverity(closedoravoid) for a window;attributionandseverityfor a rider report.
Response body
string | null
The explanation, two or three sentences and at most 320 characters. A lone sentence carries no trailing period; a longer explanation keeps its punctuation. When the two routes are the same it is the fixed sentence
"The most direct route as well as the quietest". null when no route exists, or when the difference could not be put into words.boolean
true when the SuperSafe route and the most direct route follow the same roads, or differ only by fragments under 30 m. There is nothing to explain, and summary carries the fixed sentence above.boolean
Present and
true when at least one of the two routes could not be computed, for example when the origin or destination is too far from a road. Absent otherwise.curl -X POST 'https://api.cyclemate.club/explain-supersafe/' \
-H 'apikey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"origin": [-0.0754, 51.5465],
"destination": [-0.0605, 51.5265],
"nighttime": true
}'
{
"summary": "Takes the C13 cycleway and avoids moderately busy Goldsmiths' Row. Most of the ride is on cycleways and quiet streets, for about a minute more.",
"identical": false
}
{
"summary": "The most direct route as well as the quietest",
"identical": true
}
{
"summary": null,
"identical": false,
"no_route": true
}
Variants
Night comparison
Riders choosing a route after dark are comparing different roads, because night routing prefers lit streets. Passnighttime so the sentence can mention lighting:
JavaScript
const response = await fetch('https://api.cyclemate.club/explain-supersafe/', {
method: 'POST',
headers: {
'apikey': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
origin,
destination,
nighttime: route.metadata.nighttime,
}),
});
With the route pair as context
Pass the routes you are showing so the explanation can describe the whole ride, not only the streets that differ. Buildcontext from the Personal SuperSafe option and its Direct sibling:
{
"origin": [-0.1240, 51.5308],
"destination": [-0.0865, 51.5045],
"nighttime": true,
"context": {
"safe": {
"label": "SuperSafe™ Night",
"meters": 4663,
"seconds": 1118,
"climb_meters": 25,
"descent_meters": 23,
"character_meters": { "cycleway": 1800, "quiet": 1500, "shared": 100, "moderate": 700, "busy": 500 },
"lit_share": 0.98
},
"direct": {
"label": "Direct · Night",
"meters": 4697,
"seconds": 1126,
"climb_meters": 26,
"descent_meters": 24,
"character_meters": { "cycleway": 900, "quiet": 1200, "shared": 0, "moderate": 1100, "busy": 1500 },
"lit_share": 0.91
},
"avoided": [
{ "kind": "busy_street", "name": "City Road", "meters": 412 },
{ "kind": "restricted_turn", "name": "Farringdon Street", "onto": "Charterhouse Street" },
{ "kind": "roadworks", "name": "Stonecutter Street", "schedule": "26 Aug – 19 Oct", "attribution": "Department for Transport", "severity": "closed" }
]
}
}
Comparison at a future departure
Passdeparture_time when the rider has picked a departure other than now, so closures that apply at that time are reflected in the sentence. A street market that closes a road on Saturday mornings is a difference worth naming, and only shows up when the comparison is made for that window:
{
"origin": [-0.0625, 51.5335],
"destination": [-0.0600, 51.5400],
"departure_time": "2026-08-08T11:00"
}
Error responses
400 Bad request
{
"error": "origin and destination must be [lon, lat] arrays"
}
{
"error": "nighttime must be a boolean"
}
{
"error": "departure_time is not a valid wall-clock time: tomorrow"
}
{
"error": "context.safe.meters must be a non-negative number"
}
code:
{
"error": "Sorry! This route goes outside our coverage area.",
"code": "CROSS_CITY_ROUTE"
}
401 Unauthorized
{
"error": "Invalid credentials"
}
405 Method not allowed
Method other thanPOST.
{
"error": "Method not allowed"
}
429 Too many requests
Includes aRetry-After header with the seconds until the current window closes:
{
"error": "Rate limit exceeded",
"retry_after_seconds": 42
}
500 Internal server error
{
"error": "Internal server error"
}