
Speedmap vs Open Street Map
17/07/2026Speed limits from across the world, accessed in milliseconds.
SpeedMap is a lookup API that returns the speed limit for any coordinate. Right now we’re offering coverage for the UK, Northern Ireland and the Republic of Ireland, through two endpoints with a clean, predictable JSON response.
Getting Started
Four steps to your first lookup
SpeedMap is a plain HTTPS/JSON API. No SDKs to install, no handshake beyond a bearer token.
1Authenticate with your bearer token
Every request needs a bearer token, issued to authorised customers under your service agreement. Use this on every call — we monitor usage against it.
Authorization: Bearer <your_token>
2Call the Speed endpoint
Pass a single latitude/longitude pair to get the speed limit at that point. The production base URL is https://api.iw-slu.com.
curl -X GET "https://api.iw-slu.com/Speed?latitude=51.5074&longitude=-0.1278" \ -H "Authorization: Bearer <your_token>"
3Read the response
Every response comes back in the same envelope, whether it succeeds or fails: a message, an error, and a data payload. You are not charged for requests that return an error.
{
"message": "Success",
"error": null,
"data": {
"id": 184522,
"latitude": 51.5074,
"longitude": -0.1278,
"roadName": "Whitehall",
"speedLimit": 30,
"distance": 4.2,
"errorMessage": null
}
}
4Move to bulk requests for whole journeys
Looking up more than one point? Use POST /Speed/bulk instead of looping single calls — see the endpoint reference below.
Authentication
Bearer tokens, issued per customer
Both endpoints are protected and return nothing without a valid credential. Tokens are issued to authorised customers under the terms of their service agreement.
Authorization: Bearer <your_token>
Send this header on every request. A missing or invalid token returns 401 Unauthorized with a Problem Details error body — see Errors & Rate Limits.
No self-serve portal in this version. Tokens are issued, rotated, and administered manually by Insight Warehouse. Expiry and access level are set according to the subscription tier purchased.
For a new token, a rotation, or any token issue, contact info@insight-warehouse.co.uk.
API Reference
Endpoint reference
The technical detail for developers. Commercial readers can skip ahead to the FAQ.
GET /Speed
Returns the speed limit for a single coordinate.
Query parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
latitude | number | Yes | Between -90 and 90. |
longitude | number | Yes | Between -180 and 180. |
Response fields (data)
The field that matters is speedLimit — that’s the primary data from our database. Everything else on this object (road name, class, IDs, distance) is supporting context gathered from Open Data rather than the primary result of this product.
| Field | Type | Description |
|---|---|---|
id | number | Identifier of the nearest road segment found, whether or not it’s within the match buffer. |
latitude / longitude | number | Coordinates of the matched point. |
overtureId | string (UUID) | Overture identifier of the nearest road. |
fClass | string | Functional road class — e.g. motorway, primary, residential. |
roadName | string | Name of the nearest road. |
| speedLimit | number | The match. Posted speed limit, in mph or kph depending on the locality. 0 when the point is outside the match buffer (see below) — not a real 0 mph/kph limit. |
distance | number | Distance from the queried point to the nearest road, in metres. |
errorMessage | string | Populated with a human-readable explanation when the point is outside the 20m match buffer; null on a normal match. |
Confirmed — matching criteria. A coordinate is matched to the nearest road within a 20 metre buffer. If the nearest road with a speed limit is further than 20m away, the lookup is treated as a non-match: speedLimit comes back as 0 (not a genuine 0 mph limit — read it as “no valid match”), and errorMessage explains why.
The rest of the object (roadName, fClass, distance, etc.) still describes the nearest road found, even though it fell outside the buffer — useful context, but don’t treat it as a confirmed match. 20m was set as the appropriate maximum based on extensive consultation with clients.
Example — normal match
curl 'https://api.iw-slu.com/Speed?latitude=51.5074&longitude=-0.1278' \ -H 'Authorization: Bearer <your_token>'
{
"message": "Success",
"error": null,
"data": {
"id": 184522,
"longitude": -0.1278,
"latitude": 51.5074,
"overtureId": "3fa85f64-…",
"fClass": "primary",
"roadName": "Whitehall",
"speedLimit": 30,
"distance": 4.2,
"errorMessage": null
}
}
Example — outside the 20m buffer
curl 'https://api.iw-slu.com/Speed?latitude=51.3976&longitude=-0.10382' \ -H 'Authorization: Bearer <your_token>'
{
"message": "Success",
"error": null,
"data": {
"id": 3375342,
"longitude": -0.10382,
"latitude": 51.3976,
"overtureId": "9e7c7b2f-…",
"fClass": "secondary",
"roadName": "Brigstock Road",
"speedLimit": 0,
"distance": 21.01,
"errorMessage": "The point requested is more than 20m away from any road in our network."
}
}
POST /Speed/bulk
Returns speed limits for a list of coordinate pairs. Results come back paginated — each result uses the same fields as GET /Speed above.
Request body
| Field | Type | Description |
|---|---|---|
coordinates | array | List of { latitude, longitude } pairs. Required. |
page | number | Page of results to return. |
pageSize | number | Results per page. |
search | string | Optional filter applied to results. |
Pagination fields (response)
| Field | Type | Description |
|---|---|---|
total | number | Total results across all pages. |
page / pageSize | number | Echoes the request. |
totalPages | number | Total number of pages. |
hasNextPage / hasPreviousPage | boolean | Whether more pages exist either direction. |
Example
curl -X POST 'https://api.iw-slu.com/Speed/bulk' \
-H 'Authorization: Bearer <your_token>' \
-H 'Content-Type: application/json' \
-d '{
"page": 1,
"pageSize": 100,
"coordinates": [
{ "latitude": 51.5074, "longitude": -0.1278 },
{ "latitude": 53.4808, "longitude": -2.2426 }
]
}'
{
"message": "Success",
"error": null,
"data": [
{ "id": 184522, "roadName": "Whitehall", "speedLimit": 30, "…": "…" },
{ "id": 207781, "roadName": "Deansgate", "speedLimit": 20, "…": "…" }
],
"pagination": {
"total": 2, "page": 1, "pageSize": 100,
"totalPages": 1, "hasNextPage": false, "hasPreviousPage": false
}
}
Confirmed: 500 coordinate pairs is the maximum accepted per /Speed/bulk request. Send more than that and expect a rejection — split larger jobs into multiple requests (see below).
Building a bulk request properly
Send coordinates as objects, not tuples. Each entry in the coordinates array needs explicit latitude/longitude keys — not [lat, lon] pairs or a flattened list.
"coordinates": [
{ "latitude": 51.5074, "longitude": -0.1278 },
{ "latitude": 53.4808, "longitude": -2.2426 },
{ "latitude": 55.9533, "longitude": -3.1883 }
]
Chunk large jobs into batches of 500. 500 pairs is the maximum accepted per request — split bigger jobs into batches rather than sending one large array. Think carefully about what you actually need in a batch: for example, eliminating the start or end of a journey, skipping lookups where speeds are below the minimum national speed limit for a country, or sampling at a lower frequency if appropriate.
function chunk(coords, size = 500) {
const out = [];
for (let i = 0; i < coords.length; i += size) {
out.push(coords.slice(i, i + size));
}
return out;
}
Always check hasNextPage. The bulk endpoint paginates its response independently of how many coordinates you sent. Loop on pagination.hasNextPage until it's false, incrementing page each time, rather than assuming one call returns everything.
Match results by coordinate, not array position. The reference doesn't guarantee results are returned in the same order they were submitted. Match on the returned latitude/longitude rather than assuming data[i] corresponds to coordinates[i].
Errors & Rate Limits
Status codes & error format
You are not charged for requests that return no match or a server error. Every error response, 4xx or 5xx, uses the same Problem Details format (RFC 7807).
Lookup successful. Standard envelope with the data object described above.
Bad request — missing or out-of-range coordinates.
Missing or invalid bearer token.
Rate limit exceeded. Applies per account, on both Speed and Speed/bulk — the mechanism is real and was triggered during performance testing. Confirmed: there is currently no fixed requests-per-minute or per-day threshold defined.
Unexpected server error — not billed.
Problem Details error body
Every error response (4xx or 5xx) comes back in this shape (RFC 7807). The example below is illustrative to show the structure — the actual type/title/detail values for each error case aren't enumerated in the reference documentation.
{
"type": "https://api.iw-slu.com/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid credentials.",
"instance": "/Speed"
}
To confirm — specific error codes. The data.errorMessage text for a no-match on GET /Speed is confirmed (see the worked example in the API Reference above).
What's still open is the top-level Problem Details result: the actual set of type/title/detail values a customer might see across 400/401/429/500 — for example, is there a distinct code for "coordinate out of range" vs. "malformed JSON" within a 400, or just one generic detail string? Discuss this with your dev team before building error-handling logic against it.
Coverage
Where SpeedMap has data
Coordinates outside these territories will not return a match in this version.
FAQ
Common questions
429 Too Many Requests response with a Problem Details error body. Rate limiting applies per account, on both endpoints, but there's currently no fixed requests-per-minute or per-day threshold defined. If you have a requirement for high volumes, contact us.speedLimit comes back as 0 (not a real 0 mph limit) and errorMessage explains the distance. The call still returns 200 OK — check errorMessage on the data object rather than the HTTP status.