A route that’s legal in one country can be illegal across the border at the same hour. That’s the quiet trap in European long-haul: the driving is continuous, but the rulebook changes every time the truck crosses a line. Weekend and night driving bans, sectoral bans on Alpine transit corridors, low-emission zones in city centres, country-by-country weight and dimension limits, seasonal restrictions — none of them show up on a consumer navigation app, and each one carries a real penalty when you find out the hard way: a fine, a forced wait at the roadside, or a load turned back at the frontier.
We wrote about this as one of the five road-data problems every cross-border European fleet hits. This post is the detailed version: what the data actually looks like when you stop maintaining twenty-seven national rule-scrapers and query one normalized schema instead — and how the restrictions on a corridor come back attached to the route, not as a separate homework assignment.
The Need Isn’t “Every Rule in Europe”
It’s narrower and more useful than that. A dispatcher doesn’t want a searchable encyclopedia of every national regulation; they want to know about the restrictions and the signs on this corridor, for this truck, before the driver gets there. That’s a spatial question, and it’s the shape NAPSPAN answers.
Two normalized feature types carry it, both queried from the same /features endpoint the rest of the API speaks:
truck_restrictions— the weight, height, width, length, and access limits and bans that apply to heavy vehicles, aggregated from national feeds and, across countries without a machine-readable restriction feed, from OpenStreetMap’s community truck-restriction tags.signs— roadside signage including variable-message signs (VMS/DMS): the live text a gantry is displaying, normalized so a German panel and an Austrian one read the same way in your code.
Both are filterable by jurisdiction and by a bounding box, and both hang off the trucking and infrastructure feature groups you can browse with GET /features/groups. One schema for a German night ban and an Austrian sectoral ban means you check one place instead of twenty-seven.
Restrictions on a Corridor, by Bounding Box
Ask for the truck restrictions inside a bounding box in one jurisdiction. Here’s a stretch of central Austria:
curl -H "X-API-Key: $KEY" \
"https://api.napspan.com/api/v1/features?type=truck_restrictions&jurisdiction=AUT&bbox=13.0,47.0,16.5,48.5"
Every row is a feature in the same envelope: coordinates, the road it sits on, and a properties object. The specific limit keys are source-shaped rather than a single fixed schema — an OpenStreetMap-sourced restriction carries the raw maxweight / maxheight tags (plus a derived maxweight_lbs / maxheight_ft), while a national feed carries whatever that authority publishes. What matters for routing is that the numeric ones are the keys the truck profile is actually checked against:
{
"data": [
{
"id": "OSM-EU-osm-2971775",
"source": "OSM-EU",
"jurisdiction": "AUT",
"feature_type": "truck_restrictions",
"name": "B320",
"latitude": 47.5584364,
"longitude": 14.1699315,
"road_name": "B320",
"is_active": true,
"properties": {
"maxweight": "3.5",
"maxweight_lbs": 7716,
"highway": "primary",
"source_type": "osm",
"osm_type": "way",
"osm_id": 2971775
},
"last_updated": "2026-07-06T21:10:00Z",
"has_details": false
}
],
"total": 17332, "limit": 100, "offset": 0, "has_more": true
}
OpenStreetMap-sourced restrictions carry © OpenStreetMap contributors (ODbL); where a national authority publishes its own restriction feed, that source and its licence are stamped instead.
Read the Keys That Exist, Not the Ones That Sound Canonical
This is the detail that costs an afternoon, so here it is measured rather than described. A tidy schema-shaped name — max_weight_t, max_height_m, max_width_m — reads exactly like the field you want, and matches zero rows. Across the 194,980 active truck_restrictions in the European dataset on 14 August 2026, these are the keys that actually carry a posted limit:
| Dimension | Keys that carry a value | Active rows |
|---|---|---|
| Weight | maxweight / maxweight_lbs | 73,014 / 72,831 |
| Height | maxheight / maxheight_ft | 68,067 / 40,359 |
| Width | maxwidth | 5,188 |
| Length | maxlength | 4,847 |
| Axle load | maxaxleload | 968 |
Note the types in the sample above: the raw tag is a string in whatever shape the source published it ("3.5", and elsewhere "4.2 m"), while the derived _lbs / _ft twin is a number. Parse, don’t cast.
And design for the rows with no number at all. 144,742 of the 194,980 active restrictions carry a readable dimension; the other quarter do not. Those are access bans, and they are not a data gap — they are the category a great many cross-border rules fall into. The French national layer is entirely of that kind: all 3,320 rows are regulatory orders carrying access_restriction_type: "noEntry", an issuing authority and a validity window, and not one carries a weight or a height. An integration that keys on dimensions alone silently drops France in full.
Swap type=truck_restrictions for group=trucking and the same call fans out across thirteen truck-related layers at once — restrictions and weight limits, bridge clearances, parking and rest areas, weigh and inspection stations, certified scales, freight corridors, truck routes, and repair, wash and service points — so a single request paints the whole commercial-vehicle picture for a bounding box.
The Variable-Message Signs
The signs type carries roadside signage, and for VMS/DMS gantries that includes the message the sign is currently displaying. Query it the same way — by jurisdiction, by bounding box:
curl -H "X-API-Key: $KEY" \
"https://api.napspan.com/api/v1/features?type=signs&jurisdiction=AUT&bbox=9.5,46.4,17.2,49.0"
{
"data": [
{
"id": "AUT-vms-AQ_A09_2_188,127_Q",
"source": "AUT",
"jurisdiction": "AUT",
"feature_type": "signs",
"name": "VMS A9 (AQ_A09_2_188,127_Q)",
"latitude": 47.00692,
"longitude": 15.40754,
"is_active": true,
"properties": {
"road": "A9",
"bearing": 359,
"pictograms": ["8", "24", "24", "111"],
"pictogram_descriptions": ["other", "blankVoid", "other", "other", "other"],
"image_url": "https://maps.asfinag.at/cache/wvz?image=AQ_A09_2_814_F3_F2.B_8"
},
"last_updated": "2026-08-14T11:51:37Z",
"has_details": false
}
],
"total": 17, "limit": 100, "offset": 0, "has_more": false
}
That payload is chosen deliberately, because it shows the field you must not assume. message is optional, and on some national feeds it is never present at all. Where a gantry publishes words, message carries the panel’s displayed text, newline-joined across its lines so you don’t parse a different payload per country — a Spanish panel arrives as "MANTENGA\nDISTANCIA DE\nSEGURIDAD". Where it publishes symbols, as ASFINAG’s DATEX II VMS publication does for all seventeen of its live Austrian gantries today, there is no message key and the content sits in pictograms / pictogram_descriptions, with image_url pointing at the rendered panel. Absent means this gantry does not publish text — not this gantry is blank.
Measured on 14 August 2026: of the active signs rows, Spain carries message text on 1,535 of 2,516, England on 505 of 1,519, Finland on 445 of 542. Austria, Latvia, Berlin and Poland carry none. Slovenia is the third state again — it publishes the message key on all 239 of its signs and every value is an empty string, so test the value, not the key. It is the same one signs shape either way; the national access points simply differ in what they put in it.
The Part That Matters: Restrictions Ride the Route
Querying a bounding box is the manual path. The one that changes a dispatcher’s day is that truck restrictions come back attached to the route itself. Send a normal routing request to POST /api/v1/routing/route with the truck’s profile, and the corridor’s restrictions arrive in the warnings[] channel — no second call, no separate spatial join on your side.
And it’s not a data dump. The engine compares each restriction against the truck you actually sent and only warns when your vehicle trips the limit, one warning per dimension, so the dispatcher sees exactly which measurement is the problem:
{
"warnings": [
{
"type": "truck_restriction",
"severity": "critical",
"title": "Truck weight restriction",
"description": "weight limit 3.50t; your truck is 40.00t.",
"distance_along_route_m": 184300,
"projected_arrival_time": "2026-07-08T18:40:00Z",
"source": "OSM-EU",
"source_id": "2971775",
"geometry": { "type": "Point", "coordinates": [14.1699315, 47.5584364] }
}
]
}
Everything a dispatcher needs to act is in that one object: what the restriction is, where on the route it falls (distance_along_route_m), and when the truck is projected to reach it (projected_arrival_time).
Be precise about the fourth question — is the ban even in force at that hour? — because the answer is not where you would first look. A warning carries top-level valid_from / valid_to fields, and they are populated from the feature’s own start and end times. No European truck-restriction feed we carry sets those today: on 14 August 2026, zero of the 194,980 active restrictions had a start or end time, so a restriction warning arrives with both fields absent. Where a national feed does publish a window, it arrives inside properties under the source’s own names — the French orders carry valid_from on all 3,320 rows and valid_until (not valid_to) on 420 of them — and the warning passes properties through untouched, so that is where to read it. Anything with no window at all, which includes every OpenStreetMap-tagged row above, is a standing inventory: treat it as always-applicable.
One more check rides the same request, and it fires on what you are carrying rather than how big you are. 7,102 active restrictions carry an OpenStreetMap hazmat access tag; send a load with the truck profile’s hazardous array set, and a segment tagged hazmat: "no" comes back as its own hazmat_restriction critical warning, separate from the dimension checks — the routing engine is also asked to avoid those segments via hazardous / tunnel_category. On Alpine transit that is frequently the binding constraint, and no dimension check would ever have surfaced it.
This rides the same request as the corridor toll costs and the 561/2006 break planning. One cross-border query — say LV → PL → DE — hands back a drivable truck route, the restrictions that would stop it, where the driver must legally break, and what the trip costs in tolls: the operational picture before the truck rolls, not after the fine.
What to Expect From the Data
A few honest notes so the coverage behaves the way you expect:
- Coverage is per-source and growing. On 14 August 2026 the
truck_restrictionslayer spans fifteen jurisdictions: France (3,320), Latvia (233) and Scotland (4) from national feeds, and Austria, Belgium, Switzerland, Finland, Denmark, Croatia, Lithuania, Estonia, Moldova, Luxembourg, Cyprus and Iceland from OpenStreetMap’s community tags — Austria alone accounting for 69,064 rows. Thesigns/VMS layer runs to eight: Spain, England, Finland, Slovenia, Latvia, Berlin, Poland and Austria. More national access points fold into the same two feature types as we wire them; queryGET /api/v1/features/typesfor the live per-type counts before you build against a jurisdiction. - Jurisdiction codes are ISO 3166-1 alpha-3.
?jurisdiction=LVA, neverLV— the two-letter form is not an alias and matches nothing at all, so it fails as an empty result rather than an error. Subdivisions extend it with a hyphen (GBR-SCT,DEU-BE), and a subdivision code does not roll up from its country code. - Validity windows come from the source, inside
properties. Seasonal, weekend and night bans surface where the upstream feed stamps them — today that means the French orders’properties.valid_from/properties.valid_until, not the warning’s top-levelvalid_from/valid_to, which no European restriction feed populates yet. Where a source publishes a restriction as a standing inventory without times, treat it as always-applicable rather than assuming a window. - Restrictions route; signs are queried. Truck restrictions ride the route in
warnings[]automatically. VMS/signs are not part of the routing enrichment — pull them for a corridor with abboxquery ontype=signs, keyed to the route’s bounding box. - It’s a planning input. The data is built for dispatch and pre-trip planning. It sharpens the plan; it doesn’t replace the driver’s obligation to obey the sign in front of them.
The Common Thread
Every national authority publishes its restrictions and signs its own way, on its own schedule, under its own license. Joining all of that to a route and keeping it fresh is solvable in-house — and, solved in-house, it becomes a permanent maintenance commitment that competes with the product you actually sell. NAPSPAN does that aggregation once and hands you one normalized schema: query a bounding box, or send a route and get the restrictions back on it, filterable by jurisdiction, served from Europe, every source license-classified before it ships.
Try It
- API docs —
/featureswithtype,group,jurisdiction, andbbox, plusPOST /routing/route - Live map — see the normalized restrictions and signs across the European NAPs
- Free API key — no card, 14-day trial
Know the ban before the border, not at it
National truck restrictions and variable-message signs, normalized into one schema — queryable by jurisdiction and bounding box, and surfaced automatically on your route. Free 14-day trial. No card.
Get Free API Key Read the Docs