← Back to all posts
Restrictions Cross-Border Compliance

Every Country’s Truck Ban and Sign, in One Schema

July 7, 2026 · 8 min read

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:

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:

DimensionKeys that carry a valueActive rows
Weightmaxweight / maxweight_lbs73,014 / 72,831
Heightmaxheight / maxheight_ft68,067 / 40,359
Widthmaxwidth5,188
Lengthmaxlength4,847
Axle loadmaxaxleload968

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:

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

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