<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Kotenko</title>
    <description>The latest articles on DEV Community by Roman Kotenko (@road511).</description>
    <link>https://dev.to/road511</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3869750%2F0aff7c69-b543-4a64-a0a2-32b0207973a3.png</url>
      <title>DEV Community: Roman Kotenko</title>
      <link>https://dev.to/road511</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/road511"/>
    <language>en</language>
    <item>
      <title>The Dates DOTs Publish Aren't the Dates They Hit</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 17 Sep 2026 12:28:32 +0000</pubDate>
      <link>https://dev.to/road511/the-dates-dots-publish-arent-the-dates-they-hit-5dfg</link>
      <guid>https://dev.to/road511/the-dates-dots-publish-arent-the-dates-they-hit-5dfg</guid>
      <description>&lt;p&gt;You did the responsible thing. Before quoting the haul you checked the corridor, found the ramp closure scheduled to start August 3, and planned around it. Three weeks later the truck arrives and the ramp is open — the job was pushed to August 24. Nobody lied to you. The DOT revised its own estimate eleven days ago, published the revision, and your copy of the date was simply the one you took a snapshot of.&lt;/p&gt;

&lt;p&gt;This is the failure mode that survives even after you solve the hard part. &lt;a href="https://road511.com/blog/planned-construction-api.html" rel="noopener noreferrer"&gt;Getting forward-looking schedules into one queryable schema&lt;/a&gt; answers &lt;em&gt;what is planned&lt;/em&gt;. It does not tell you &lt;strong&gt;how much that plan is worth&lt;/strong&gt; — and a start date with no track record behind it is a number, not information.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Date Field Is a Snapshot. You Need the Record.
&lt;/h2&gt;

&lt;p&gt;Every planned-construction record we carry has an &lt;code&gt;estimated_start_time&lt;/code&gt; and an &lt;code&gt;estimated_end_time&lt;/code&gt;. Read them today and you get the agency’s current intent. Read them tomorrow and you may get a different answer, with nothing in the payload to tell you it changed, how far it moved, or whether this is the fourth time.&lt;/p&gt;

&lt;p&gt;So we keep the change log. Every time a source republishes a project with a different projected start or end, we record the old value, the new value, and when we saw it. That log is a first-class, queryable dataset — not an internal audit trail — and it converts a static date into something with a history you can weigh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eleven Weeks of Watching Dates Move
&lt;/h2&gt;

&lt;p&gt;Here is what the log actually holds as of 17 September 2026, on live North American data — not an illustration. These are readings, not constants: run the queries yourself and you will get today’s numbers, which is the point of the thing being an endpoint.&lt;/p&gt;

&lt;p&gt;Across the &lt;strong&gt;2,269 active planned-construction projects&lt;/strong&gt; we carry (Virginia 910, Delaware 775, West Virginia 257, New Jersey 247, North Carolina 77, and three in New Brunswick), we have recorded &lt;strong&gt;21,289 date revisions&lt;/strong&gt; in the window running 3 July to 17 September 2026 — 12,307 revisions to a projected start, 8,982 to a projected end. Those revisions land on &lt;strong&gt;4,414 distinct projects&lt;/strong&gt;, 289 of which are still on the board right now.&lt;/p&gt;

&lt;p&gt;The single most useful thing in that pile is the direction. Of the 21,289 revisions, &lt;strong&gt;21,128 moved a date later and 161 moved one earlier.&lt;/strong&gt; Schedule drift is not noise scattered around a central estimate; it is a one-way ratchet. A published date is, in practice, a floor.&lt;/p&gt;

&lt;p&gt;The size of the moves is where planning slack actually comes from:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How far a projected start moved, per revision&lt;/th&gt;
&lt;th&gt;Revisions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exactly one week&lt;/td&gt;
&lt;td&gt;3,438&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More than a week&lt;/td&gt;
&lt;td&gt;2,752&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two to six days&lt;/td&gt;
&lt;td&gt;2,376&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Under a day (or pulled earlier)&lt;/td&gt;
&lt;td&gt;2,147&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exactly one day&lt;/td&gt;
&lt;td&gt;1,594&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The median revision moves a start &lt;strong&gt;seven days&lt;/strong&gt;; the average is 5.8, pulled down by the sub-day nudges and the handful of moves that went the other way. The largest single revision we have on record pushed a ramp closure on I-81S at mile marker 291.2 from July 15 to September 14 — &lt;strong&gt;61 days in one edit&lt;/strong&gt;, published on 13 September. And &lt;strong&gt;1,657 separate projects&lt;/strong&gt; have taken at least one start revision larger than a week.&lt;/p&gt;

&lt;p&gt;None of that is knowable from a date field. All of it is knowable from the log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying It
&lt;/h2&gt;

&lt;p&gt;The change log is one endpoint. Filter it by what changed, on what kind of feature, over what window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/analytics/feature-history?
  feature_type=future_construction&amp;amp;
  change_type=estimated_start_time_change&amp;amp;
  limit=2

{
  "data": [
    {
      "id": 174240967,
      "feature_id": "VA-fcon-WZSW4664041-08182026-3",
      "source": "VA",
      "feature_type": "future_construction",
      "change_type": "estimated_start_time_change",
      "changed_field": "estimated_start_time",
      "old_value": "2026-09-15T10:30:00Z",
      "new_value": "2026-09-18T10:30:00Z",
      "recorded_at": "2026-09-17T11:26:47.870007Z"
    },
    {
      "id": 174240964,
      "feature_id": "VA-fcon-WZSW4638782-06152026-5",
      "source": "VA",
      "feature_type": "future_construction",
      "change_type": "estimated_start_time_change",
      "changed_field": "estimated_start_time",
      "old_value": "2026-09-11T11:00:00Z",
      "new_value": "2026-09-18T11:00:00Z",
      "recorded_at": "2026-09-17T11:26:47.870007Z"
    }
  ],
  "total": 12307,
  "limit": 2,
  "offset": 0,
  "has_more": true
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;change_type&lt;/code&gt; for &lt;code&gt;estimated_end_time_change&lt;/code&gt; to watch completion dates instead, add &lt;code&gt;feature_id&lt;/code&gt; to pull the full history of one project you care about, or bound it with &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; and poll for what moved since your last run. The same endpoint carries &lt;code&gt;created&lt;/code&gt;, &lt;code&gt;deactivated&lt;/code&gt; and &lt;code&gt;reactivated&lt;/code&gt; entries, so a project quietly dropped from an agency’s feed shows up as an event too, rather than just vanishing from your result set.&lt;/p&gt;

&lt;p&gt;Entries are newest-first. The change log is analytics-tier (Pro and up); the &lt;code&gt;future_construction&lt;/code&gt; records themselves are on every plan, free trial included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Things Look Identical and Mean Opposite Things
&lt;/h2&gt;

&lt;p&gt;This is the part worth internalizing before you wire an alert, because the naive version of this monitor will page you every night.&lt;/p&gt;

&lt;p&gt;The most-revised project in our data is &lt;code&gt;VA-fcon-WZNO4585395-12252025-137&lt;/code&gt; — work on the I-66E Express Lanes between mile markers 43 and 65. Between July 3 and September 16 its dates were revised &lt;strong&gt;118 times&lt;/strong&gt; (59 starts and 59 ends, in lockstep). That sounds like a project in freefall. It isn’t. Read the log and the pattern is unmistakable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;estimated_start_time  2026-07-02T13:00Z → 2026-07-03T13:00Z
estimated_start_time  2026-07-03T13:00Z → 2026-07-04T13:00Z
estimated_start_time  2026-07-04T13:00Z → 2026-07-05T13:00Z
estimated_start_time  2026-07-05T13:00Z → 2026-07-06T13:00Z
                              ... every night ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same eight-hour window, rolled forward by exactly one day, every day. That is not a job slipping — it is a &lt;strong&gt;recurring daily closure&lt;/strong&gt; published as a single record whose window always points at the next occurrence. (The one break in the rhythm is instructive too: on July 11 the record went inactive, came back July 12, and jumped three days instead of one. It skipped the weekend.)&lt;/p&gt;

&lt;p&gt;Now contrast a genuine slip. &lt;strong&gt;810 Virginia projects&lt;/strong&gt; took between three and five start revisions that add up to a net move of more than two weeks — distinct edits, days apart, each one pushing a real start date further out. The worst of them moved 75 days across four edits between 21 July and 14 September. Same endpoint, same &lt;code&gt;change_type&lt;/code&gt;, completely different operational meaning.&lt;/p&gt;

&lt;p&gt;The rule that separates them is simple and worth building into whatever you wire on top: &lt;strong&gt;a stream of uniform one-day moves is a schedule shape; a few irregular multi-day moves is a schedule failing.&lt;/strong&gt; The 1,594 exactly-one-day start revisions in the table above are mostly the former. The 2,752 start revisions over a week are the ones that should reach a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Is Worth Downstream
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pad the quote with evidence, not superstition.&lt;/strong&gt; If the corridor’s projects historically move seven days later and roughly one start revision in five moves more than a week, that is a number you can put in a bid instead of a gut feeling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the jobs on your lanes.&lt;/strong&gt; Poll &lt;code&gt;feature-history&lt;/code&gt; with &lt;code&gt;from&lt;/code&gt; set to your last check and get told when a date on a corridor you run has moved — before the truck finds out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-check permits that were issued against a moved date.&lt;/strong&gt; An oversize permit written around an August closure window is worth revalidating when the closure walks into September.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank your sources honestly.&lt;/strong&gt; A project revised once and executed is a different planning risk from one revised six times, and the log is the only place that difference exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter the noise out of your own alerts.&lt;/strong&gt; Rolling nightly windows are a real slice of raw revisions but not the bulk of them — moves of exactly 24 hours are 1,568 of the 12,307 start revisions, about one in eight. Knowing which slice you are looking at up front is the difference between a useful monitor and one everybody mutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Honest Limits
&lt;/h2&gt;

&lt;p&gt;Four of them, and they matter more than the headline numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today this signal is Virginia.&lt;/strong&gt; All 21,289 revisions in the window come from VDOT’s feed — every last one. Delaware, New Jersey, West Virginia, North Carolina and the rest published &lt;em&gt;zero&lt;/em&gt; date revisions over the same eleven weeks — not because their projects run on time, but because those feeds don’t republish a revised date on the same record; a changed plan tends to arrive as a new record instead. We would rather say that plainly than average six jurisdictions together and imply a continental drift statistic we cannot support. As more sources start revising in place, the same query picks them up with no change on your side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The window is eleven weeks, not six years.&lt;/strong&gt; The change log’s oldest entry of any kind is May 20, 2026, but construction date revisions only start on July 3 — that is the baseline behind every number above, and quoting the earlier date would overstate it. These are real observations over a short window — enough to characterize how dates move, not enough to claim a seasonal or multi-year pattern. It deepens every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We record what the agency published, not why.&lt;/strong&gt; A revision tells you the date moved and when we saw it move. Whether that was weather, utilities, a contractor, or a clerical fix is not in any feed we consume, and we won’t invent a reason field to make the data look richer than it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An estimate remains an estimate.&lt;/strong&gt; The point of the log is not to replace the DOT’s forecast with ours. It is to tell you how much that forecast has moved so far, which is the only honest input to how much slack you should carry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Mechanism, Beyond Construction
&lt;/h2&gt;

&lt;p&gt;Schedule drift isn’t unique to roadwork — it is a property of anything published with an expected end. The same log carries &lt;strong&gt;25,107 revisions to the projected end of weather alerts&lt;/strong&gt; across 12,281 alerts, which is what a warning being extended hour by hour looks like when you record it instead of overwriting it. Any time-bounded feature type we carry — alerts, special events, seasonal restrictions — leaves the same trail, reachable with the same &lt;code&gt;feature_type&lt;/code&gt; filter.&lt;/p&gt;

&lt;p&gt;That is the general shape of the thing: not a new dataset, but the refusal to throw away the previous value. Most of the road-data problems we write about in &lt;a href="https://road511.com/blog/north-american-road-data-challenges.html" rel="noopener noreferrer"&gt;the five road-data problems every NA fleet team hits&lt;/a&gt; come down to a feed telling you only about now. Keeping the record is what makes a plan auditable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — &lt;code&gt;/analytics/feature-history&lt;/code&gt;, plus the &lt;code&gt;future_construction&lt;/code&gt; surface and its scheduled-window filters&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://road511.com/blog/planned-construction-api.html" rel="noopener noreferrer"&gt;Planned construction, end to end&lt;/a&gt; — how the projects themselves are queried&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/construction-schedule-drift-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=construction-schedule-drift-api&amp;amp;utm_content=dev-10" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>logistics</category>
      <category>trucking</category>
      <category>data</category>
    </item>
    <item>
      <title>What a Large-Vehicle Router Cannot Know</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:56:20 +0000</pubDate>
      <link>https://dev.to/road511/what-a-large-vehicle-router-cannot-know-3n93</link>
      <guid>https://dev.to/road511/what-a-large-vehicle-router-cannot-know-3n93</guid>
      <description>&lt;p&gt;Google Maps Platform took Large Vehicle Routing to GA last month — height, weight, length, width, axle count and hazmat class in the Routes API, US-wide. If you have ever had to explain to a dispatcher why a consumer routing API sent a 13'6" trailer under an 11'9" bridge, this is good news, and it makes truck-legal routing a commodity. Everyone will have it.&lt;/p&gt;

&lt;p&gt;So here is the part that does not become a commodity.&lt;/p&gt;

&lt;p&gt;A router avoids a low bridge because somebody published a height for it. That is the whole mechanism. And the layer underneath is much thinner and much messier than a rendered map suggests.&lt;/p&gt;

&lt;p&gt;I aggregate US and Canadian DOT feeds for a living. On the morning of 15 September 2026 our store held &lt;strong&gt;715,434 active bridge-clearance records from 53 state and provincial sources&lt;/strong&gt;. Of those, &lt;strong&gt;105,479 — 14.7% — carry a vertical clearance value at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The other 85% are not corrupt. They are federal NBI structural inventory: &lt;code&gt;structure_number&lt;/code&gt;, &lt;code&gt;deck_area_sqm&lt;/code&gt;, &lt;code&gt;year_built&lt;/code&gt;, &lt;code&gt;last_inspection_date&lt;/code&gt;, &lt;code&gt;scour_critical&lt;/code&gt;. The registry knows the bridge exists and when it was last inspected. It was never designed to record what fits underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twenty-seven names for one measurement
&lt;/h2&gt;

&lt;p&gt;Where a height does exist, it arrives under &lt;strong&gt;27 different property names&lt;/strong&gt; across those sources. There are 30 clearance-related keys in all; three of them are &lt;em&gt;horizontal&lt;/em&gt; clearances, a different measurement that must not be pooled in.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;key&lt;/th&gt;
&lt;th&gt;rows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clearance_m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;92,473&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clearance_ft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;91,964&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;deck_clearance_ft&lt;/code&gt; / &lt;code&gt;deck_clearance_m&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;10,908 each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vertical_clearance_m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5,307&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vertical_clearance_under_ft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vertical_clearance_ft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,516&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vertical_clearance_label&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lane_1_clearance&lt;/code&gt; … &lt;code&gt;lane_9_clearance&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,881 – 2,050 each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;posted_height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,038&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;posted_clearance_ft&lt;/code&gt; / &lt;code&gt;posted_clearance_in&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;645 each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;left_&lt;/code&gt; / &lt;code&gt;center_&lt;/code&gt; / &lt;code&gt;right_clearance_in&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;645 each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vertical_clearance_raw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;509&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;vertical_clearance_on_ft&lt;/code&gt; / &lt;code&gt;_over_ft&lt;/code&gt; / &lt;code&gt;_deck_ft&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;30 – 243 each&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Normalizing names is tedious but mechanical. This is the one that is not — a real record, unedited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"clearance_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"clearance_ft"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14.6"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One measurement, one object, two JSON types. The metric value is a number; the imperial value of the same physical distance is a string. Across the store that split is total and consistent: every one of the 92,473 rows with &lt;code&gt;clearance_m&lt;/code&gt; has it as a number, and every one of the 91,964 rows with &lt;code&gt;clearance_ft&lt;/code&gt; has it as a string.&lt;/p&gt;

&lt;p&gt;That is the kind of thing that passes a schema check, passes a null check, passes a range check, and then silently fails a comparison in a language where &lt;code&gt;4.45 &amp;lt; 4.6&lt;/code&gt; and &lt;code&gt;"14.6" &amp;lt; "4.6"&lt;/code&gt; are both true and mean different things. If your clearance test is one careless comparison away from that, it will be correct in staging for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is not just clearances
&lt;/h2&gt;

&lt;p&gt;Same shape, every layer a truck actually cares about — active rows and distinct jurisdictions, same snapshot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;layer&lt;/th&gt;
&lt;th&gt;rows&lt;/th&gt;
&lt;th&gt;jurisdictions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;truck restrictions&lt;/td&gt;
&lt;td&gt;1,477,809&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bridge clearances&lt;/td&gt;
&lt;td&gt;715,434&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rail crossings&lt;/td&gt;
&lt;td&gt;162,284&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weight restrictions&lt;/td&gt;
&lt;td&gt;22,498&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weigh stations&lt;/td&gt;
&lt;td&gt;975&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tunnel clearances&lt;/td&gt;
&lt;td&gt;580&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Posted weight restrictions: seven jurisdictions. Not seven that &lt;em&gt;have&lt;/em&gt; weight limits — seven that publish them in a feed anyone can consume, and that is our ingestion coverage rather than a census of the law. Spring load restrictions apply across most of the northern half of the continent; they are &lt;em&gt;machine-readable&lt;/em&gt; in a handful of places.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;The ceiling on any large-vehicle router — Google's, HERE's, mine — is publisher coverage, not routing quality. The routing converges. The data does not, and it is the part that decides whether a legal-looking route is actually legal.&lt;/p&gt;

&lt;p&gt;If you are building on top of one of these APIs, the useful design question is what your system does with an absence. A blank is a legitimate answer and it should reach the driver as a blank. The failure mode is interpolating one — filling a missing clearance from a neighbouring structure, or defaulting a missing weight limit to "no limit". We leave it empty. A blank is a blank.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I build Road511, a road-data API over US and Canadian 511 systems. The counts above are our own normalized store; the underlying feeds are public agency data, and the numbers move — these were pulled on the morning of 15 September 2026.)&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/truck-legal-routing-data-coverage.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=truck-legal-routing-data-coverage&amp;amp;utm_content=dev-13" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>data</category>
      <category>postgres</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>561/2006 Tells You When to Stop. It Doesn't Tell You If the Bay Is Free.</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 10 Sep 2026 13:18:40 +0000</pubDate>
      <link>https://dev.to/road511/5612006-tells-you-when-to-stop-it-doesnt-tell-you-if-the-bay-is-free-15dj</link>
      <guid>https://dev.to/road511/5612006-tells-you-when-to-stop-it-doesnt-tell-you-if-the-bay-is-free-15dj</guid>
      <description>&lt;p&gt;Regulation (EC) 561/2006 is arithmetic. Four and a half hours of driving, then a 45-minute break. Nine hours a day, eleven twice a week, a daily rest of at least eleven hours. Every planning tool worth the name can compute where the clock runs out, and ours does it too.&lt;/p&gt;

&lt;p&gt;The regulation says nothing about whether there is anywhere to put the truck.&lt;/p&gt;

&lt;p&gt;That is the part dispatchers actually lose sleep over, because the failure mode is not a fine — it is a driver at 21:30 standing in the entrance of a full lorry park, choosing between the hard shoulder and driving on illegally. And it is a genuinely hard problem to plan around, because the map is misleading in a very specific way: &lt;strong&gt;the lot does not disappear when it fills up.&lt;/strong&gt; Every static inventory keeps showing it, at full size, all night.&lt;/p&gt;

&lt;h2&gt;
  
  
  “Is There a Lorry Park?” Is the Wrong Question
&lt;/h2&gt;

&lt;p&gt;Take Austria, where we have bay-level truth to check the map against.&lt;/p&gt;

&lt;p&gt;The static inventory says the ASFINAG motorway and expressway network carries &lt;strong&gt;244 truck-parking sites with 9,185 truck bays&lt;/strong&gt;. That is the number a coverage map shows, and it is correct. It is also useless after dark, because it describes the concrete, not the availability.&lt;/p&gt;

&lt;p&gt;Of those, &lt;strong&gt;85 sites — 5,835 truck bays — publish live occupancy&lt;/strong&gt;, which we poll every &lt;strong&gt;60 seconds&lt;/strong&gt;. We have been recording every change for those sites since 30 June 2026: &lt;strong&gt;293,334 observations&lt;/strong&gt; as of 10 September 2026, of which &lt;strong&gt;127,387&lt;/strong&gt; fall inside the trailing thirty days the trend endpoint reads. Bucketing that history by hour of day produces the curve that the static map cannot show you — measured here over the thirty days to 10 September 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Local time (Austria, CEST)&lt;/th&gt;
&lt;th&gt;Truck bays occupied&lt;/th&gt;
&lt;th&gt;Avg. free bays per site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;08:00 – 14:00&lt;/td&gt;
&lt;td&gt;~39%&lt;/td&gt;
&lt;td&gt;~41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:00&lt;/td&gt;
&lt;td&gt;52.4%&lt;/td&gt;
&lt;td&gt;31.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:00&lt;/td&gt;
&lt;td&gt;68.4%&lt;/td&gt;
&lt;td&gt;21.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:00&lt;/td&gt;
&lt;td&gt;76.6%&lt;/td&gt;
&lt;td&gt;15.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;21:00 – 04:00 (plateau; peak 77.0% at 01:00)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~76–77%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~16&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00:00 – 04:00&lt;/td&gt;
&lt;td&gt;~75–77%&lt;/td&gt;
&lt;td&gt;~16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Network-wide occupancy &lt;strong&gt;doubles between late morning and nightfall&lt;/strong&gt; — 38.4% at 11:00 local, 76.6% at 21:00 — and then holds on that plateau until about four in the morning, peaking at 77.0% around 01:00, before it falls away to 57.5% by six. Read as a fleet average, that still sounds survivable: 77% occupied means 23% free, and 16 open bays per site is not nothing.&lt;/p&gt;

&lt;p&gt;One arithmetic note, because getting it wrong moves that column by twenty points. Those percentages are &lt;strong&gt;truck bays occupied&lt;/strong&gt;, computed against each site’s &lt;code&gt;truck_spaces&lt;/code&gt;. Divide the same free-bay counts by each site’s total &lt;code&gt;capacity&lt;/code&gt; instead and the network reads 68–88% occupied all day, which is not a busier network — it is a numerator and a denominator that count different things. 84 of the 85 reporting Austrian sites are mixed sites where the two differ.&lt;/p&gt;

&lt;p&gt;That average is the trap. Occupancy does not distribute itself politely across the network; it concentrates, and individual lots go hard to zero:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;40 of the 86 sites observed in the thirty-day window hit zero free bays&lt;/strong&gt; at some point.&lt;/li&gt;
&lt;li&gt;At &lt;strong&gt;23:00 local, 22 of the 85&lt;/strong&gt; sites observed in that hour had been completely full at least once during the window — at &lt;strong&gt;21:00, 33 of them&lt;/strong&gt; had, and at &lt;strong&gt;07:00, three&lt;/strong&gt;. &lt;strong&gt;Eight lots record a zero in every single hour from 18:00 to 23:00.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Take one ordinary lot — Wiener Neustadt, 40 truck bays. Average free bays at 07:00 local: &lt;strong&gt;22.0&lt;/strong&gt;. At 20:00 local: &lt;strong&gt;5.7&lt;/strong&gt;. Every hourly bucket from 18:00 to 23:00 local has a recorded minimum of &lt;strong&gt;0&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wiener Neustadt is not an exceptional site. It is a normal one, and for the entire second half of the evening its honest answer to “can my driver stop here?” is &lt;em&gt;probably not, and you should have known that at dispatch.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile the same network at 14:47 local on 10 September looked almost relaxed: &lt;strong&gt;zero of 85 sites full&lt;/strong&gt;, and exactly one lot down to its last two bays. Arnoldstein at the Italian border was sitting at 8% occupancy with 366 of its 402 truck bays open. Both pictures are true. They are six hours apart. A number that is right in the afternoon and wrong at bedtime has to be read live, or it is not worth reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Occupancy, in the Same &lt;code&gt;/features&lt;/code&gt; Call as Everything Else
&lt;/h2&gt;

&lt;p&gt;ASFINAG publishes this as two DATEX II documents: a static &lt;code&gt;ParkingTablePublication&lt;/code&gt; describing the sites, and a dynamic &lt;code&gt;ParkingStatusPublication&lt;/code&gt; carrying free-space counts. Neither is usable alone — the status document has no geometry, and the table has no occupancy. We join them on &lt;code&gt;parkingRecordReference&lt;/code&gt; and publish one feature per site with the live counts folded in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.napspan.com/api/v1/features?type=truck_parking&amp;amp;jurisdiction=AUT"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUT-tp-PARK000064"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"feature_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"truck_parking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Arnoldstein Zollamt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;46.540813&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.670096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"truck_parking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;431&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"truck_spaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"available_spots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;366&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"occupied_spots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"occupancy_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"occupancy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spacesAvailable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"opening_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"occupancy_updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-10T12:33:34Z"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"last_updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-10T12:36:35Z"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details in that payload matter more than they look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;truck_spaces&lt;/code&gt; is the denominator, not &lt;code&gt;capacity&lt;/code&gt;.&lt;/strong&gt; Arnoldstein has 431 spaces of which 402 take a lorry, and &lt;code&gt;available_spots&lt;/code&gt; counts truck bays only. Divide by &lt;code&gt;capacity&lt;/code&gt; and you will quietly under-report how full the truck side is at every mixed site on the network — and that is 84 of the 85 Austrian sites that report. The rule generalises with one branch: use &lt;code&gt;truck_spaces&lt;/code&gt; where the feed publishes it, and &lt;code&gt;capacity&lt;/code&gt; where it does not. Austria stamps &lt;code&gt;truck_spaces&lt;/code&gt; on all 244 of its sites; the German federal feed stamps it on none, because its lorry-parking records are truck-only and &lt;code&gt;capacity&lt;/code&gt; already is the truck figure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;available_spots: 0&lt;/code&gt; is a fact, not a gap.&lt;/strong&gt; It means &lt;em&gt;known full&lt;/em&gt;. A site with no live feed omits the field entirely. Those two states must not collapse into one in your code — treat “absent” as unknown and “0” as closed to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;occupancy_updated&lt;/code&gt; is the count’s own timestamp&lt;/strong&gt;, separate from &lt;code&gt;last_updated&lt;/code&gt;, so you can age out a stale reading on your own threshold rather than trusting our poll cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the jurisdiction code is &lt;strong&gt;ISO 3166-1 alpha-3&lt;/strong&gt;. &lt;code&gt;?jurisdiction=AUT&lt;/code&gt;, never &lt;code&gt;AT&lt;/code&gt; — the two-letter form is not an alias, it returns an empty result set.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trend: “Is This Lot Typically Full at My ETA?”
&lt;/h2&gt;

&lt;p&gt;A live count answers a question about now. Dispatch plans a stop for six hours from now, when the live count will have changed. That gap is what the per-hour history is for, and it comes back on the feature-detail endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.napspan.com/api/v1/features/AUT-tp-PARK000402/details"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUT-tp-PARK000402"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wr. Neustadt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"availability_trend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"avg_available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avg_capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low_samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avg_available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avg_capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low_samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avg_available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"avg_capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low_samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avg_available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"avg_capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low_samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty-four buckets, built from the trailing thirty days of recorded occupancy for that specific lot. It is the difference between “there are two bays free right now” and “this lot has been down to single digits every evening for a month” — and only the second one is actionable at 14:00 when you are deciding where the driver sleeps.&lt;/p&gt;

&lt;p&gt;Three things to know before you plot it. &lt;strong&gt;Hours are UTC&lt;/strong&gt;; Austria ran at UTC+2 across this window, so bucket 18 is 20:00 on the driver’s dashboard clock — localize before you show it to a human. &lt;code&gt;samples&lt;/code&gt; is there to be read: buckets are recorded on change, so a thin bucket deserves less confidence than a fat one. We publish the count rather than smoothing it away.&lt;/p&gt;

&lt;p&gt;And the third is the one that will silently skew a chart: &lt;strong&gt;&lt;code&gt;avg_capacity&lt;/code&gt; is the site’s total capacity, while &lt;code&gt;avg_available&lt;/code&gt; counts truck bays only.&lt;/strong&gt; Wiener Neustadt has 40 truck bays inside 71 total spaces, which is why every bucket above reads &lt;code&gt;avg_capacity: 71&lt;/code&gt;. Computing &lt;code&gt;1 - avg_available / avg_capacity&lt;/code&gt; is therefore &lt;em&gt;not&lt;/em&gt; this lot’s truck occupancy — it mixes the two counts and reads about twenty points too full. Take the denominator from the feature’s &lt;code&gt;truck_spaces&lt;/code&gt; and use &lt;code&gt;avg_capacity&lt;/code&gt; only for what it is: the size of the whole site. It is the same trap as the live payload, one field name further along, and the fact that the two numbers sit side by side in the same object is exactly what makes it easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Actually Belongs: On the Route, Next to the Break
&lt;/h2&gt;

&lt;p&gt;Querying a lot by id is the debugging path. The one that changes an evening is that occupancy arrives already attached to the route, and to the break the regulation demands.&lt;/p&gt;

&lt;p&gt;Send a normal truck route to &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt; with &lt;code&gt;include_features: ["truck_parking"]&lt;/code&gt;, and parking comes back in the &lt;code&gt;features[]&lt;/code&gt; channel — distinct from hazard &lt;code&gt;warnings[]&lt;/code&gt;, opt-in, and costing nothing when you do not ask for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"truck_parking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wr. Neustadt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"distance_along_route_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;184000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"detour_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"side"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"available_spots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"capacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-30T18:02:11Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Point"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;16.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;47.8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add an &lt;code&gt;hos&lt;/code&gt; block with the driver’s clock and &lt;code&gt;ruleset: "eu_561"&lt;/code&gt;, and the response gains an &lt;code&gt;hos[]&lt;/code&gt; projection: where the break falls, and which parking is reachable &lt;em&gt;before&lt;/em&gt; the limit rather than after it. Where a feed publishes live occupancy, two things follow that a static inventory can never do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reachable parking is ordered by open spaces&lt;/strong&gt;, so the first suggestion is the one most likely to still have room, not merely the nearest concrete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The stop is flagged &lt;code&gt;feasible: false&lt;/code&gt; when every reachable lot reports full.&lt;/strong&gt; That is the alert worth having. It fires at planning time, in an API response, hours before it would otherwise arrive as a phone call from a lay-by.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It rides the same request as the corridor’s &lt;a href="https://napspan.com/blog/cross-border-truck-restrictions-vms.html" rel="noopener noreferrer"&gt;truck bans and VMS signs&lt;/a&gt;, its &lt;a href="https://napspan.com/blog/toll-cost-routing-api.html" rel="noopener noreferrer"&gt;toll costs&lt;/a&gt;, its &lt;a href="https://napspan.com/blog/eu-border-queue-wait-times.html" rel="noopener noreferrer"&gt;border queues&lt;/a&gt;, and the &lt;a href="https://napspan.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;561/2006 break plan&lt;/a&gt; itself. One call returns a drivable truck route, the rules that would stop it, what it costs, where the driver must legally rest — and whether there will be a bay there when they arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Data Does Not Do
&lt;/h2&gt;

&lt;p&gt;The honest limits are part of the product, so here they are without decoration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most national feeds publish inventory, not occupancy.&lt;/strong&gt; Slovenia, the Netherlands, Bavaria and Latvia all hand us sites and capacities with no availability whatsoever, and the same is true of most of the general &lt;code&gt;parking&lt;/code&gt; feeds. Two feeds are the exception, and the larger one is German: the federal motorway lorry-parking layer carries live counts on &lt;strong&gt;1,759 of its 1,858 sites&lt;/strong&gt;, twenty times Austria’s reporting network, and it is live in the API in the same &lt;code&gt;truck_parking&lt;/code&gt; schema. This post is built on Austria anyway, for a reason worth stating plainly: ASFINAG’s terms are a confirmed CC BY 4.0, while the German federal licence variant is still being verified, and we would rather anchor a worked example on the layer whose redistribution terms we have finished checking. Query both; weigh the German one accordingly until we can say more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even in Austria it is 85 of 244 sites.&lt;/strong&gt; The other 159 are real lots that appear with capacity and no counts. Your UI needs a third state — free / full / &lt;em&gt;not reporting&lt;/em&gt; — and the absence of &lt;code&gt;available_spots&lt;/code&gt; is what selects it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thirty days of history is thirty days, and it is a moving window.&lt;/strong&gt; The Austrian series starts 30 June 2026 — 293,334 observations by 10 September — but the trend endpoint reads only the trailing thirty days, so the record has already outgrown the window and what you get back today is a summer month, not the whole series. The difference is not academic: in the thirty days to 30 July, 57 of the 86 lots had hit zero free bays and 47 of the sites observed at 23:00 had been full at some point; in the thirty days to 14 August those figures are 40 and 29, and in the thirty days to 10 September, 40 and 22. Same network, same endpoint, weeks apart. Use the trend as a base rate for the season you are in, and re-pull it rather than caching a number from last month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A free bay is not a reserved bay.&lt;/strong&gt; Nothing here books anything. It tells you what the operator reported and when, which is what you need to choose between two lots — not a guarantee that the space survives the next twenty minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The trend is a prior, not a prediction.&lt;/strong&gt; An hour-of-day average over thirty days is a solid base rate and it is blind to the specific Thursday your driver is having. Use it to rank candidates and to know when the network tightens; use the live count for the final call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Common Thread
&lt;/h2&gt;

&lt;p&gt;This is the same shape as the &lt;a href="https://napspan.com/blog/european-road-data-challenges.html" rel="noopener noreferrer"&gt;five road-data problems every cross-border European fleet hits&lt;/a&gt;. The information exists. A public authority publishes it, properly, under an open licence. It arrives split across two DATEX II documents that have to be joined on a reference key, in a national profile, on its own cadence — and doing that once per country, forever, is a maintenance commitment that competes with the product you actually sell.&lt;/p&gt;

&lt;p&gt;NAPSPAN does that join once. Austrian bay counts arrive in the same &lt;code&gt;truck_parking&lt;/code&gt; schema as every other parking feed in the system, filterable by jurisdiction, carried on the route beside the tolls and the rest breaks, served from Europe, and licence-classified before it ships. The Austrian data is published by ASFINAG under CC BY 4.0 — &lt;em&gt;Datenquelle: ASFINAG — licensed under CC BY 4.0&lt;/em&gt; — and that credit travels with every record we hand you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://napspan.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — &lt;code&gt;/features&lt;/code&gt; with &lt;code&gt;type=truck_parking&lt;/code&gt;, &lt;code&gt;/features/{id}/details&lt;/code&gt; for the trend, and &lt;code&gt;POST /routing/route&lt;/code&gt; with &lt;code&gt;include_features&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.napspan.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — the Austrian network as it stands right now&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.napspan.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://napspan.com/blog/truck-parking-occupancy-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=truck-parking-occupancy-api&amp;amp;utm_content=dev-10" rel="noopener noreferrer"&gt;napspan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logistics</category>
      <category>api</category>
      <category>trucking</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>One Bridge, Two Border Waits — and Only One of Them Is Yours</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Sat, 05 Sep 2026 11:58:08 +0000</pubDate>
      <link>https://dev.to/road511/one-bridge-two-border-waits-and-only-one-of-them-is-yours-h78</link>
      <guid>https://dev.to/road511/one-bridge-two-border-waits-and-only-one-of-them-is-yours-h78</guid>
      <description>&lt;p&gt;A cross-border load has one place where the plan reliably falls apart, and it isn’t traffic. It’s the primary inspection lane. Everything upstream — the route, the hours, the appointment at the consignee — is built on an ETA that assumes the truck rolls through the plaza. Twenty-five minutes in the commercial queue is a missed dock window; ninety is a reset of the whole day.&lt;/p&gt;

&lt;p&gt;The wait is published. Both governments publish it, in fact — and that is exactly where it gets interesting, because &lt;strong&gt;they publish different halves of it&lt;/strong&gt;. Here is the Blue Water Bridge between Port Huron, Michigan and Point Edward, Ontario, as our North American production database held it &lt;strong&gt;shortly after 07:00 UTC on 8 August 2026&lt;/strong&gt; — 3 a.m. in Michigan, the quietest hour of the day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Publisher&lt;/th&gt;
&lt;th&gt;Commercial wait&lt;/th&gt;
&lt;th&gt;Reported at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Into the US (northbound plaza, Port Huron)&lt;/td&gt;
&lt;td&gt;U.S. Customs and Border Protection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;“At Midnight EST”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Into Canada (Point Edward)&lt;/td&gt;
&lt;td&gt;Canada Border Services Agency&lt;/td&gt;
&lt;td&gt;&lt;code&gt;25 minutes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-08-08 02:22 EDT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same bridge. Same minute. Zero one way, twenty-five the other — and neither agency publishes the other one’s direction. CBP’s feed covers arrivals into the United States. CBSA’s covers arrivals into Canada; in that same snapshot, its US-bound commercial column read &lt;code&gt;--&lt;/code&gt; at &lt;strong&gt;all 29&lt;/strong&gt; of its offices, because that isn’t CBSA’s number to report. If your app looked up “the Blue Water Bridge wait” from one source, there was a coin-flip chance it was quoting a queue your truck will never sit in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Both Halves, One Field, On the Route
&lt;/h2&gt;

&lt;p&gt;Road511 polls both feeds every five minutes and normalizes them into the same &lt;code&gt;border_crossings&lt;/code&gt; feature type. On a truck-aware route they arrive as &lt;code&gt;warnings[]&lt;/code&gt; entries of &lt;code&gt;type: "border_crossing"&lt;/code&gt; — the same array that already carries bridge clearances, work zones and weigh stations, from the same single call to &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here are the two sides of that bridge exactly as the routing enrichment renders them, from the property blobs quoted above (abridged — &lt;code&gt;distance_along_route_m&lt;/code&gt; and &lt;code&gt;projected_arrival_time&lt;/code&gt; depend on your own route):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blue Water Bridge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Commercial border wait ~25 min."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08T07:03:04Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CBSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commercial_wait_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commercial_canada_bound"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25 minutes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commercial_us_bound"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Point Edward, ON/Port Huron, MI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"last_updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08 02:22 EDT"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Port Huron - Bluewater Bridge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Commercial border wait ~0 min."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08T07:10:43Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commercial_wait_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wait_trend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"steady"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24 hrs/day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"port_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commercial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"delay_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no delay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"trend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"steady"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lanes_open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_lanes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fast_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lanes Closed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"update_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"At Midnight EST"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"passenger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"delay_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lanes_open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_lanes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One field means the same thing on both: &lt;code&gt;commercial_wait_minutes&lt;/code&gt;, an integer, commercial lanes, always minutes. Underneath it the raw agency shapes are passed through untouched, so nothing is lost if you want to read CBP’s lane counts or CBSA’s own wording.&lt;/p&gt;

&lt;p&gt;Note that there are two clocks here, and they are different clocks. &lt;code&gt;as_of&lt;/code&gt; is &lt;em&gt;our&lt;/em&gt; observation time — when we last confirmed this record in an upstream poll, minutes old on a five-minute cycle. The agency’s own stamp is a separate field kept in &lt;code&gt;properties&lt;/code&gt;: CBSA’s &lt;code&gt;last_updated&lt;/code&gt; (“2026-08-08 02:22 EDT” above) and CBP’s &lt;code&gt;update_time&lt;/code&gt; (“At Midnight EST”), each in the agency’s own wording and timezone. A fresh &lt;code&gt;as_of&lt;/code&gt; means we are certain the feed still says this; it does not mean the agency re-measured the queue a minute ago. If your logic needs to know how old the &lt;em&gt;reading&lt;/em&gt; is rather than how old the fetch is, read the agency field.&lt;/p&gt;

&lt;p&gt;Severity is keyed on the commercial wait, because this is a truck API and the passenger queue is not your problem: &lt;code&gt;info&lt;/code&gt; below 15 minutes, &lt;code&gt;warning&lt;/code&gt; from 15, &lt;code&gt;critical&lt;/code&gt; past 45 minutes or whenever the port is reported closed. That makes the whole thing filterable with the knob you already have — &lt;code&gt;"min_severity": "warning"&lt;/code&gt; returns only the crossings on your route that are actually going to cost you time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The direction caveat, stated up front
&lt;/h3&gt;

&lt;p&gt;We do not yet resolve which side of the crossing your truck is on. When both directions are known, the warning carries &lt;strong&gt;the larger of the two&lt;/strong&gt; — deliberately, so a route never under-warns — which means a southbound run into Michigan can read a Canada-bound figure. Resolving direction from the route heading is the next iteration of this feature. Until it ships, treat &lt;code&gt;commercial_wait_minutes&lt;/code&gt; as “the worst commercial wait reported at this crossing right now”, and read the per-direction fields underneath it if you need to be exact. We would rather write that sentence than let you discover it on a load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Vocabularies for One Number
&lt;/h2&gt;

&lt;p&gt;The two agencies do not merely publish different directions — they publish different &lt;em&gt;kinds of thing&lt;/em&gt;. CBP sends a structured integer per lane group. CBSA sends free text. Across the 29 CBSA offices in that snapshot the Canada-bound commercial column held &lt;code&gt;No Delay&lt;/code&gt; (21 offices), four actual figures (&lt;code&gt;3 minutes&lt;/code&gt;, &lt;code&gt;5 minutes&lt;/code&gt;, &lt;code&gt;12 minutes&lt;/code&gt;, &lt;code&gt;25 minutes&lt;/code&gt;), &lt;code&gt;Not Applicable&lt;/code&gt; (3), and &lt;code&gt;Temporarily closed&lt;/code&gt; (1).&lt;/p&gt;

&lt;p&gt;Normalizing that is the boring part of the job and the part that has to be right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;No Delay&lt;/code&gt; → &lt;code&gt;0&lt;/code&gt;. A real, published zero.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;25 minutes&lt;/code&gt; → &lt;code&gt;25&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--&lt;/code&gt;, &lt;code&gt;N/A&lt;/code&gt;, &lt;code&gt;Not Applicable&lt;/code&gt;, empty → &lt;strong&gt;no number at all&lt;/strong&gt;. Not zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last line is the one that matters. “This agency does not report this direction” and “there is no queue” are opposite facts, and collapsing them into &lt;code&gt;0&lt;/code&gt; is how a dispatch board ends up confidently green in front of a two-hour queue. When nothing parseable is there, the warning stays &lt;code&gt;info&lt;/code&gt; and says &lt;code&gt;"Border crossing on route."&lt;/code&gt; — a crossing you should know about, with no wait claimed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sumas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Border crossing on route."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08T07:05:42Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CBP"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  “No Delay” With Two Lanes Open
&lt;/h2&gt;

&lt;p&gt;Look again at the Port Huron block: &lt;code&gt;"lanes_open": 2, "max_lanes": 9&lt;/code&gt;. The zero-minute wait is true — and it is true of a plaza running two of its nine commercial lanes at three in the morning. Detroit’s Ambassador Bridge in the same snapshot: &lt;strong&gt;2 of 13&lt;/strong&gt; commercial lanes open, no delay. Buffalo’s Peace Bridge: &lt;strong&gt;4 of 8&lt;/strong&gt;, no delay.&lt;/p&gt;

&lt;p&gt;A wait figure is a measurement of the trucks that are there now. Lane capacity is the thing that decides what happens when the morning wave arrives, and it is in the payload for exactly that reason. If you are building an arrival-time model rather than a dashboard light, &lt;code&gt;lanes_open&lt;/code&gt; against &lt;code&gt;max_lanes&lt;/code&gt; is the more interesting series of the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closed Is a State, Not an Absence
&lt;/h2&gt;

&lt;p&gt;In that snapshot, &lt;strong&gt;27 of the 81 CBP ports were reported closed&lt;/strong&gt; — 24 of them on the Mexican border, where limited hours are normal (54 of the 81 ports run 24 hrs/day; the rest keep windows like &lt;code&gt;6 am-10 pm&lt;/code&gt; or &lt;code&gt;8 am-4 pm&lt;/code&gt;). Laredo’s World Trade Bridge, a commercial-only crossing, was shut: it was 2 a.m. in Texas and its published hours are &lt;code&gt;7 am-Midnight&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A closed port comes back as &lt;code&gt;critical&lt;/code&gt;, with the closure in the title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laredo - World Trade Bridge — closed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Border crossing reported closed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"closed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08T07:00:15Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CBP"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it stays in the data while it is closed. That is a deliberate design rule and worth stating, because the obvious implementation gets it backwards: if a crossing disappeared from the API the moment it shut, a dispatcher planning a 4 a.m. departure would see nothing on the corridor and read that as “no border crossing here” rather than “the gate is down until seven.” Silence is the one answer a routing API must never give to that question. A closed port is still a port, it has published hours, and it will open.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Much of It Is Actually Populated
&lt;/h2&gt;

&lt;p&gt;Now the part most vendors leave out. In that same snapshot, of the 81 CBP ports, &lt;strong&gt;17 carried a commercial delay figure&lt;/strong&gt;. The other 64 were &lt;code&gt;Update Pending&lt;/code&gt; (36), &lt;code&gt;Lanes Closed&lt;/code&gt; (15) or &lt;code&gt;N/A&lt;/code&gt; (13). On the CBSA side, 21 offices reported &lt;code&gt;No Delay&lt;/code&gt;, three &lt;code&gt;Not Applicable&lt;/code&gt;, one &lt;code&gt;Temporarily closed&lt;/code&gt;, and four reported an actual number. FAST-lane figures — which we surface as &lt;code&gt;fast_lane_wait_minutes&lt;/code&gt; and a derived &lt;code&gt;fast_lane_savings_minutes&lt;/code&gt; when they are published — existed at exactly &lt;strong&gt;one&lt;/strong&gt; port. (That last figure in particular is an artefact of the hour, and we come back to it below.)&lt;/p&gt;

&lt;p&gt;Some of that is the hour: a plaza with its commercial lanes closed overnight has no wait to report, and the numbers thicken as the border wakes up. But the shape is the point — &lt;strong&gt;a border-wait API is mostly a “no figure right now” API&lt;/strong&gt;, and a client that assumes every crossing has a live number will be wrong most of the time. Ours tells you which case you are in, on every single crossing, rather than smoothing it into a plausible-looking integer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Query at Ten in the Morning
&lt;/h2&gt;

&lt;p&gt;“Some of that is the hour” is the kind of hedge that deserves a measurement rather than a promise, so here is one. Everything above is a 3 a.m. reading. We re-ran the identical queries against the same production database on &lt;strong&gt;14 August 2026 at 13:57 UTC — 09:57 Eastern, mid-morning on a working Friday&lt;/strong&gt;. The difference is large enough that you should not size your integration on the overnight figures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;3 a.m. ET, 8 Aug&lt;/th&gt;
&lt;th&gt;10 a.m. ET, 14 Aug&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CBP ports carrying a commercial delay figure&lt;/td&gt;
&lt;td&gt;17 of 81&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35 of 81&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CBP ports reported closed&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highest commercial wait anywhere&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30 min&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ports publishing a FAST figure&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CBSA offices publishing a US-bound wait&lt;/td&gt;
&lt;td&gt;0 of 29&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 of 27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those rows carry a lesson each. &lt;strong&gt;The population rate roughly doubles&lt;/strong&gt; and the closures nearly vanish — so a coverage number quoted from an overnight sample understates the daytime product badly, and one quoted from a daytime sample overstates the overnight one. Whichever direction you are being sold, ask what hour the sample came from. &lt;strong&gt;The FAST count moved by a factor of eleven&lt;/strong&gt;, which is the honest correction to the paragraph above: FAST is not a feature that exists at one port, it is a feature CBP populates when the FAST lanes are staffed. At mid-morning it was publishing FAST at eleven ports, and at Laredo’s Colombia Solidarity bridge the FAST lane read &lt;code&gt;5&lt;/code&gt; against a standard &lt;code&gt;30&lt;/code&gt; — a genuine 25-minute saving, and exactly the case an enrolled carrier wants surfaced.&lt;/p&gt;

&lt;p&gt;And the last row is the one that did &lt;em&gt;not&lt;/em&gt; move. Every one of CBSA’s 27 offices still had &lt;code&gt;--&lt;/code&gt; in its US-bound commercial column at ten in the morning, just as all 29 did at three. That asymmetry is structural, not a nocturnal quirk — it is the design fact this whole post is built on, and it is stable at every hour we have looked.&lt;/p&gt;

&lt;p&gt;The lane-capacity story sharpens in daylight too. Detroit’s Ambassador Bridge had gone from &lt;strong&gt;2 of 13&lt;/strong&gt; commercial lanes to &lt;strong&gt;5 of 13&lt;/strong&gt;, and had picked up a 5-minute wait doing it — more lanes and a longer queue, at the same time, which is precisely why the wait figure alone is a poor arrival-time input. Port Huron ran &lt;strong&gt;4 of 9&lt;/strong&gt; with a 2-minute wait. Best of all, Laredo’s World Trade Bridge — the commercial-only crossing that was shut at 2 a.m. Texas time in the earlier snapshot — was open with &lt;strong&gt;11 of its 19&lt;/strong&gt; lanes running and no delay at all. The closed record you saw overnight had turned into the busiest commercial plaza in the dataset, without ever leaving the API.&lt;/p&gt;

&lt;p&gt;The northern border, meanwhile, was quiet: the Peace Bridge, the Ambassador, the Windsor Tunnel, Alexandria Bay, Sweetgrass and Blaine all sat at 5 minutes or under. The 30-minute figures were both on the Mexican border, at Laredo’s Colombia Solidarity and Pharr. If your lanes are Canadian, budget your expectations accordingly — and if they are Mexican, note that the busiest reading in this dataset is still a half-hour, not the horror stories.&lt;/p&gt;

&lt;p&gt;The wider inventory works the same way, and it is the coverage number to plan against. As of 14 August 2026 we hold &lt;strong&gt;197 active border crossings&lt;/strong&gt; across North America; &lt;strong&gt;108 of them&lt;/strong&gt; — 81 CBP ports and 27 CBSA offices — sit behind a live wait feed. The remaining &lt;strong&gt;89&lt;/strong&gt; come from provincial and state 511 map layers — Manitoba (32), Saskatchewan (24), Alberta (12), New Brunswick (6), Québec (5), Newfoundland (4), British Columbia (4) and California (2) — and are location records: they tell you a crossing exists and where, and they publish no wait at all, ever. They still ride your route as &lt;code&gt;info&lt;/code&gt;, never with an invented number.&lt;/p&gt;

&lt;p&gt;Read that ratio before you design around this: &lt;strong&gt;45% of the land border crossings we know about have no wait feed behind them at any hour&lt;/strong&gt;, and no amount of waiting for daytime will change it — unlike the &lt;code&gt;Update Pending&lt;/code&gt; case above, this is a permanent absence rather than a temporary one. Note also that the CBSA office count itself drifts: it was 29 on 8 August and 27 on 14 August, because the agency’s own list of reporting offices changes. Don’t hard-code it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Don’t Do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No history, no “typical Thursday.”&lt;/strong&gt; We publish the live reading and a poll-over-poll &lt;code&gt;wait_trend&lt;/code&gt; (&lt;code&gt;rising&lt;/code&gt; / &lt;code&gt;falling&lt;/code&gt; / &lt;code&gt;steady&lt;/code&gt;, moving on a ±5-minute threshold so a two-minute wobble doesn’t flap). There is no border-wait time series behind this endpoint yet, so no hour-of-day base rate — and we would rather say so than dress a single reading up as a pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No modelled waits.&lt;/strong&gt; Every figure is the agency’s own, or there is no figure. No crowd-sourced estimate, no inference of a queue from silence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No bypass or clearance claims.&lt;/strong&gt; FAST is surfaced when CBP publishes it. Whether your carrier is enrolled, and what that means at primary, is between you and the program.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Same Field on the Other Side of the Atlantic
&lt;/h2&gt;

&lt;p&gt;The schema is not North-America-shaped by accident. Our European deployment runs the identical &lt;code&gt;border_crossing&lt;/code&gt; warning over Ukraine’s electronic queue at the EU external frontier, where commercial waits are measured in &lt;em&gt;days&lt;/em&gt; rather than minutes — the same &lt;code&gt;commercial_wait_minutes&lt;/code&gt; integer, rendered as “4 d 3 h” instead of “25 min”. If you run both continents, it’s one integration. That story is &lt;a href="https://napspan.com/blog/eu-border-queue-wait-times.html" rel="noopener noreferrer"&gt;on the NAPSPAN blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html#truck-routing" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — the full &lt;code&gt;warnings[]&lt;/code&gt; reference, including &lt;code&gt;border_crossing&lt;/code&gt; severity bands and properties&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://road511.com/blog/truck-routing-hazard-warnings.html" rel="noopener noreferrer"&gt;Truck routing with hazard warnings&lt;/a&gt; — how the whole corridor &lt;code&gt;warnings[]&lt;/code&gt; channel works&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://road511.com/data-sources.html" rel="noopener noreferrer"&gt;Data sources&lt;/a&gt; — every feed behind the API, with its licence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait times from U.S. Customs and Border Protection (US public domain) and the Canada Border Services Agency. Contains information licensed under the Open Government Licence – Canada, © Canada Border Services Agency. Crossing coordinates are geocoded from OpenStreetMap data (© OpenStreetMap contributors, ODbL). Figures in this post come from two snapshots of our production database: the overnight one taken shortly after 07:00 UTC on 8 August 2026 (03:00 Eastern), and the mid-morning comparison taken at 13:57 UTC on 14 August 2026 (09:57 Eastern). Each was correct at that moment only, and every section states which one it is drawing on — query the API for the live picture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/us-canada-border-wait-times-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=us-canada-border-wait-times-api&amp;amp;utm_content=dev-11" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>logistics</category>
      <category>trucking</category>
      <category>data</category>
    </item>
    <item>
      <title>Know the Conditions at the Incident, Not Just That There Is One</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 27 Aug 2026 08:06:53 +0000</pubDate>
      <link>https://dev.to/road511/know-the-conditions-at-the-incident-not-just-that-there-is-one-354d</link>
      <guid>https://dev.to/road511/know-the-conditions-at-the-incident-not-just-that-there-is-one-354d</guid>
      <description>&lt;p&gt;“Crash on I-80, right lane blocked.”&lt;/p&gt;

&lt;p&gt;That sentence describes two completely different events. In one, it’s a dry afternoon, the lane clears in twenty minutes, and the right call is to hold the load and wait. In the other, the pavement is iced, visibility is down to a quarter mile, and the wind is gusting across an exposed grade — in which case the crash you can see is the least interesting fact on the screen, because the conditions that produced it are still there, and they are about to produce another one three miles up.&lt;/p&gt;

&lt;p&gt;Every incident feed on the market gives you the first sentence. Almost none of them tell you which of the two events it actually is. &lt;strong&gt;Dispatch doesn’t respond to incidents. Dispatch responds to incidents in context&lt;/strong&gt; — and the context has to arrive attached to the incident, at the moment it appears, or it arrives too late to change the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context Already Exists, Sitting in a Different Feed
&lt;/h2&gt;

&lt;p&gt;State DOTs have spent decades installing roadside weather stations — RWIS, in the trade — the instrumented poles that report air temperature, wind, visibility, humidity, precipitation type, and, critically, &lt;em&gt;what the road surface itself is doing&lt;/em&gt; — dry, wet, icy, snow-covered. Each agency says that in its own words rather than a shared vocabulary, which turns out to matter a great deal and which we come back to in detail below.&lt;/p&gt;

&lt;p&gt;Road511 currently carries &lt;strong&gt;4,082 active roadside weather stations across 37 states and provinces&lt;/strong&gt;, alongside &lt;strong&gt;8,132 active road-condition segments across 20&lt;/strong&gt; — the latter being the DOT’s own segment-level assessment of a stretch of road, as opposed to a single station’s point reading. (Counts measured 27 August 2026, active records only. Stations come and go as agencies retire hardware, so treat these as this month’s figure rather than a constant.)&lt;/p&gt;

&lt;p&gt;The data is public, official, and already flowing. The problem is that it lives in a different layer from the incidents, on a different schema, keyed to different geometry. So the dispatcher gets an incident in one panel and a weather map in another, and does the spatial join in their head, under time pressure, at 3am. That join is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every Event Carries Its Own Weather
&lt;/h2&gt;

&lt;p&gt;When a new incident lands in Road511 — a crash, a closure, a work zone — we immediately look for the nearest active weather station within 50 km and write down what it was reporting at that moment. That reading is stapled to the event, permanently. Here is an actual record, pulled from the production database as this post was last revised — a Pennsylvania crash on I‑70 from the afternoon of 16 August 2026, verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/analytics/event-weather/PA-evt-348347

{
  "id": 2188447,
  "event_id": "PA-evt-348347",
  "distance_km": 1.7816931,
  "temperature": 80.1,
  "temperature_c": 26.722221,
  "wind_speed": 3.6,
  "wind_direction": "SW",
  "visibility": 12.43,
  "precipitation": "No Precipitation",
  "road_surface": "Dry",
  "humidity": 83,
  "reading_recorded_at": "2026-08-16T17:00:10.704866Z",
  "created_at": "2026-08-16T17:04:39.913287Z"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event itself reads &lt;em&gt;CRASH on I-70 Eastbound at ramp from PA 51 South. All lanes disrupted.&lt;/em&gt; Read the weather as a dispatcher rather than as a developer. Dry pavement, no precipitation, a visibility of 12.43 — miles, on this feed — almost no wind, from a station 1.8 km away, recorded four minutes before the incident was written. That is a &lt;em&gt;hold the load and wait&lt;/em&gt; incident, and you knew it without reading the description. Flip three of those fields — a road surface the agency has flagged for ice, visibility under a mile, a crosswind on an exposed grade — and the same incident text supports the opposite decision: reroute the following three loads now, don’t just delay them. The value here is not that the conditions are dramatic. It is that they are &lt;em&gt;present&lt;/em&gt;, on the incident, at the moment it appears, in one call.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;distance_km&lt;/code&gt; field is doing real work there, and it’s the reason we return it. A station 1.8 km from the incident is describing the incident. A station 46 km away is describing a different valley, and you should discount it accordingly. Across the whole archive the average snapshot sits about 16 km out, so this is a filter you will actually use. We don’t hide that number behind a confidence score we made up — we give you the distance and let you decide what it’s worth.&lt;/p&gt;

&lt;h3&gt;
  
  
  What those field values actually look like
&lt;/h3&gt;

&lt;p&gt;Notice that &lt;code&gt;precipitation&lt;/code&gt; reads &lt;code&gt;"No Precipitation"&lt;/code&gt; and &lt;code&gt;road_surface&lt;/code&gt; reads &lt;code&gt;"Dry"&lt;/code&gt; — capitalised, spelled out, in the reporting agency’s own words. &lt;strong&gt;These are not normalized enums, and you must not code against them as though they were.&lt;/strong&gt; Across the archive the same two columns hold &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;No Precipitation&lt;/code&gt;, &lt;code&gt;not available&lt;/code&gt;, &lt;code&gt;Unknown&lt;/code&gt;, &lt;code&gt;light rain&lt;/code&gt;, &lt;code&gt;rain moderate&lt;/code&gt; and &lt;code&gt;0 mm&lt;/code&gt; for precipitation; &lt;code&gt;Dry&lt;/code&gt;, &lt;code&gt;dry&lt;/code&gt;, &lt;code&gt;Wet&lt;/code&gt;, &lt;code&gt;Trace Moisture&lt;/code&gt;, &lt;code&gt;Trace Wet&lt;/code&gt;, &lt;code&gt;Moist&lt;/code&gt;, &lt;code&gt;wet and dry&lt;/code&gt;, &lt;code&gt;Ice Watch&lt;/code&gt;, &lt;code&gt;Ice Warning&lt;/code&gt;, &lt;code&gt;Snow Warning&lt;/code&gt; and &lt;code&gt;Slushy&lt;/code&gt; for road surface. A handful of feeds leak raw sensor codes into the field — you will find literal &lt;code&gt;4.0&lt;/code&gt; and &lt;code&gt;65535&lt;/code&gt; values in there.&lt;/p&gt;

&lt;p&gt;Two consequences worth internalising before you build on this. First, &lt;strong&gt;match case-insensitively and on substrings&lt;/strong&gt;, and treat anything you don’t recognise as unknown rather than as safe. Second, and less obvious: some agencies publish their ice-detection subsystem’s &lt;em&gt;alarm state&lt;/em&gt; in this field rather than an observation, which is why &lt;code&gt;Ice Watch&lt;/code&gt; turns up on a Virginia road at 30 °C in August. &lt;code&gt;Ice Watch&lt;/code&gt; means a sensor is armed, not that there is ice. If you are going to escalate on a surface value, escalate on it &lt;em&gt;together with&lt;/em&gt; the temperature, never on the string alone. We pass the agency’s word through rather than mapping it to a tidy enum, because the mapping would have to guess at exactly the cases where guessing is most expensive — but that means the interpretation is genuinely yours to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Anecdote to a Number You Can Argue With
&lt;/h2&gt;

&lt;p&gt;“Bad weather causes accidents” is a thing everyone knows and nobody can put a figure on. Once an event carries a snapshot, the figure becomes a query. Group the incident history by what the road was doing and what was falling out of the sky:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/analytics/weather-correlation?jurisdiction=OH

[
  { "precipitation": "None",    "road_surface": "",
    "event_count": 7348, "avg_severity": 1.95, "avg_temp_c": 22.0 },
  { "precipitation": "None",    "road_surface": "Dry",
    "event_count": 5806, "avg_severity": 1.95, "avg_temp_c": 24.8 },
  { "precipitation": "None",    "road_surface": "Unknown",
    "event_count": 2000, "avg_severity": 1.94, "avg_temp_c": 24.7 },
  { "precipitation": "Unknown", "road_surface": "",
    "event_count": 1709, "avg_severity": 1.98, "avg_temp_c": 21.9 },
  { "precipitation": "Other",   "road_surface": "",
    "event_count": 1229, "avg_severity": 1.96, "avg_temp_c": 23.4 },
  { "precipitation": "None",    "road_surface": "Wet",
    "event_count":  530, "avg_severity": 2.05, "avg_temp_c": 22.0 },
  { "precipitation": "Other",   "road_surface": "Unknown",
    "event_count":  470, "avg_severity": 2.00, "avg_temp_c": 24.3 },
  { "precipitation": "Unknown", "road_surface": "Unknown",
    "event_count":  461, "avg_severity": 2.02, "avg_temp_c": 27.1 },
  { "precipitation": "Rain",    "road_surface": "",
    "event_count":  445, "avg_severity": 2.04, "avg_temp_c": 19.0 },
  { "precipitation": "Rain",    "road_surface": "Wet",
    "event_count":  256, "avg_severity": 2.09, "avg_temp_c": 21.9 }
]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the real response for Ohio on 16 August 2026 — the top ten rows exactly as returned, in the order returned — and it is worth showing you exactly because it is &lt;em&gt;not&lt;/em&gt; the tidy result a marketing example would invent. Three things in it are the honest state of this endpoint today, and you should know all three before you build on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The severity spread is currently tiny.&lt;/strong&gt; 1.95 on dry, 2.05 on wet, 2.09 in rain — the ordering is the one you would predict, but the whole range spans about a seventh of a single severity step, and further down the same response &lt;code&gt;Trace Wet&lt;/code&gt; sits at 1.79, &lt;em&gt;below&lt;/em&gt; dry. Treat that as a gradient thin enough that one unusual month could erase it, not as a finding. (&lt;code&gt;avg_severity&lt;/code&gt; is a weighted score: critical counts 4, major 3, moderate 2, everything else 1.) We are not going to tell you we have measured that ice doubles incident severity, because in this archive, so far, we have not. What we have built is the instrument; the reading it currently gives on most lanes is “no strong effect yet.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The archive is young.&lt;/strong&gt; Weather snapshots begin on &lt;strong&gt;10 April 2026&lt;/strong&gt;. There is no winter in this data at all yet, which is the single biggest reason the ice story above cannot be told from it — the interesting conditions have not happened during the archive’s lifetime. A date range starting before April 2026 returns an empty array, so don’t write one and conclude the endpoint is broken. The first real winter of this dataset is the 2026–27 one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most rows have an empty &lt;code&gt;road_surface&lt;/code&gt;.&lt;/strong&gt; Of the &lt;strong&gt;646,539&lt;/strong&gt; snapshots in the archive on 27 August 2026, &lt;strong&gt;118,860&lt;/strong&gt; carry a road-surface string and &lt;strong&gt;133,766&lt;/strong&gt; a precipitation string; only &lt;strong&gt;22,964 — about 3.6% — carry surface, precipitation, wind and visibility all four&lt;/strong&gt;. Most stations report a subset, and the grouping key you most want is the one most often absent. Ohio is better than that average, at roughly half its rows carrying a surface string, and it still leads with an empty one: that top row is not a bug, it is 7,348 events whose nearest station published a temperature and nothing about the pavement.&lt;/p&gt;

&lt;p&gt;Filter by &lt;code&gt;jurisdiction&lt;/code&gt;, by event &lt;code&gt;type&lt;/code&gt;, by &lt;code&gt;severity&lt;/code&gt;, or by date range, and you can put the question your own lanes actually pose to the data — which corridors punish ice, which ones shrug it off. Just budget for the answer being “not enough observations yet” on a lot of them, and check &lt;code&gt;event_count&lt;/code&gt; before you believe an &lt;code&gt;avg_severity&lt;/code&gt;. We would rather hand you a thin dataset labelled thin than a thick one we smoothed.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Route
&lt;/h2&gt;

&lt;p&gt;Weather stations also ride the corridor. Send a route to &lt;code&gt;POST /routing/route&lt;/code&gt; and the stations near the line come back in &lt;code&gt;warnings[]&lt;/code&gt; as &lt;code&gt;type: "weather"&lt;/code&gt;, each carrying its readings and the time the truck is projected to reach it — the same channel that already carries &lt;a href="https://road511.com/blog/truck-routing-hazard-warnings.html" rel="noopener noreferrer"&gt;bridge clearances and corridor hazards&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Be clear about what that is and isn’t. Those weather markers are surfaced at &lt;code&gt;info&lt;/code&gt; severity, as observations. &lt;strong&gt;Road511 does not currently threshold them&lt;/strong&gt; — there is no rule that escalates “road_surface: ice” to a &lt;code&gt;warning&lt;/code&gt;, no wind-speed limit that trips a flag for a high-profile trailer. The reading is handed to you; the policy is yours, because the policy is genuinely yours: the wind speed that stops a loaded reefer is not the one that stops a flatbed, and we’d rather give you the number than guess your fleet’s tolerance. When you write that policy, apply the two cautions above — the surface strings are the agency’s vocabulary rather than an enum, and wind and visibility carry no unit — because a threshold is exactly where an unnormalized field does its damage.&lt;/p&gt;

&lt;p&gt;And the segment-level &lt;code&gt;road_conditions&lt;/code&gt; layer — the DOT’s own “this stretch is snow-covered” assessment — is &lt;em&gt;not&lt;/em&gt; enriched onto routes today. It’s queryable as a layer over any bounding box or corridor you define, but it does not currently arrive attached to a route response. If route-level surface state is what you need, that’s a gap, and we’d rather tell you now than have you discover it in integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Build With It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triage that reads the conditions, not just the headline&lt;/strong&gt; — auto-escalate an incident whose snapshot shows a freezing temperature &lt;em&gt;and&lt;/em&gt; an icy surface string, before a human has read the description. (Both halves: the surface string alone will fire on a summer sensor alarm.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Winter corridor scorecards&lt;/strong&gt; — rank your lanes by the severity they produce under the same conditions. The endpoint for this exists today; the archive to fill it starts accumulating its first winter in November 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-incident review that isn’t guesswork&lt;/strong&gt; — when a load is late or a claim is filed, the conditions at the event are already recorded, timestamped, and attributable to a named agency’s station.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Driver-facing condition alerts&lt;/strong&gt; — surface the observations along the route with your own thresholds applied, tuned to the trailer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather beside everything else on the corridor&lt;/strong&gt; — conditions alongside &lt;a href="https://road511.com/blog/work-zones-wzdx-routing-api.html" rel="noopener noreferrer"&gt;work zones&lt;/a&gt;, &lt;a href="https://road511.com/blog/planned-construction-api.html" rel="noopener noreferrer"&gt;planned construction&lt;/a&gt;, and closures in one schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Provenance, and the Honest Limits
&lt;/h2&gt;

&lt;p&gt;These readings come from state DOT roadside stations — official instruments, not a modelled forecast and not crowd-sourced. That’s the strength, and it dictates the limits, which are worth stating plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The snapshot is taken once, at the moment the event is created, and never refreshed.&lt;/strong&gt; It answers “what were the conditions when this appeared,” which is the question that matters for triage and for later analysis. It does not track how the weather evolved over the life of a six-hour closure. For current conditions, query the station directly — &lt;code&gt;/analytics/weather&lt;/code&gt; returns its reading time-series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If no active station sits within 50 km, the event gets no snapshot at all.&lt;/strong&gt; We do not reach further and pretend a reading from the next county describes this stretch of road. On a rural corridor, expect gaps; a missing snapshot is an honest absence, not a zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readings pass through in the units the reporting agency publishes.&lt;/strong&gt; Most US stations report Fahrenheit, miles, and miles per hour — but not all of them: British Columbia and, of all places, California publish Celsius. So &lt;code&gt;temperature&lt;/code&gt; is the agency’s own number, and &lt;code&gt;temperature_c&lt;/code&gt; sits beside it, normalized, for when you need to compare or average across jurisdictions. Where we haven’t established a feed’s unit, &lt;code&gt;temperature_c&lt;/code&gt; is simply absent rather than guessed — roughly a quarter of snapshots carry it — and the correlation endpoint leaves those readings out of its average instead of blending them in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temperature is the only field we normalize. Wind and visibility are not.&lt;/strong&gt; This matters more than it sounds: in the same &lt;code&gt;visibility&lt;/code&gt; column you will find &lt;code&gt;12.43&lt;/code&gt; from a Delaware station reporting miles and &lt;code&gt;621&lt;/code&gt; from a Virginia one that plainly is not. There is no unit field to tell them apart. Compare visibility and wind &lt;em&gt;within a single jurisdiction&lt;/em&gt; and never across them, and put a sanity bound on the value before you feed it to anything that makes a decision. If you need one cross-jurisdiction number, &lt;code&gt;temperature_c&lt;/code&gt; is the one field that is safe to average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is observation, not prediction.&lt;/strong&gt; There is no weather forecasting here, and we’re not going to bolt a forecast model onto a road-data API and call it dispatch intelligence. What we do is guarantee that when an incident exists, whatever the nearest station measured exists next to it, in one schema, from 4,082 stations we keep parsed and current as their feeds drift — so that job isn’t yours.&lt;/p&gt;

&lt;p&gt;Weather snapshots, the correlation endpoint, and the reading time-series are analytics-tier features (Pro and above). The underlying &lt;code&gt;weather_stations&lt;/code&gt; and &lt;code&gt;road_conditions&lt;/code&gt; layers are available on every plan, free trial included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — the analytics surface, plus the &lt;code&gt;weather_stations&lt;/code&gt; and &lt;code&gt;road_conditions&lt;/code&gt; feature types&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — see the station network and current road conditions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/weather-aware-dispatch-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=weather-aware-dispatch-api&amp;amp;utm_content=dev-09" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>trucking</category>
      <category>api</category>
      <category>logistics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Plan Around Next Month's Closure, Not Today's</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Tue, 11 Aug 2026 10:16:35 +0000</pubDate>
      <link>https://dev.to/road511/plan-around-next-months-closure-not-todays-fge</link>
      <guid>https://dev.to/road511/plan-around-next-months-closure-not-todays-fge</guid>
      <description>&lt;p&gt;Every traffic API on the market answers the same question: &lt;em&gt;what is closed right now?&lt;/em&gt; That question is worth answering. It is also the wrong one for most of the decisions a planner actually makes. The oversize load ships in three weeks. The seasonal lane is being quoted for August. The construction crew is scheduling a detour route for a job that starts after Labor Day. Nobody planning that work cares what is coned off this morning — they care what will be dug up &lt;em&gt;when the truck gets there&lt;/em&gt;, and a live incident feed structurally cannot tell them.&lt;/p&gt;

&lt;p&gt;So the planner does the thing the tooling forces: opens six state DOT project pages in six browser tabs, reads six different PDF schedules, and writes the dates into a spreadsheet that is stale within a week. &lt;strong&gt;The data exists. It is published. It just isn’t queryable as data.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned Work Is a Different Dataset Than Active Work
&lt;/h2&gt;

&lt;p&gt;It’s tempting to assume that future construction is just a work zone with a start date in the future, and that whatever feed carries today’s lane closures will carry next month’s too. It doesn’t, and the distinction matters operationally.&lt;/p&gt;

&lt;p&gt;Active work zones — the ones we wrote about in &lt;a href="https://road511.com/blog/work-zones-wzdx-routing-api.html" rel="noopener noreferrer"&gt;work zones along a route&lt;/a&gt; — come from real-time feeds describing conditions that exist right now. They appear when the cones go out and disappear when the crew leaves. Their whole design assumes you are asking about the present.&lt;/p&gt;

&lt;p&gt;Planned construction comes from somewhere else entirely: the agency’s project pipeline. A DOT’s capital program, its STIP project list, the “upcoming closures” page maintained by the district office. These records exist before a single cone is placed — but how far before depends entirely on the agency, and that variation is the first thing to plan around. Delaware’s pipeline carries start dates booked into 2030; Virginia’s layer is overnight lane work on a roughly three-day horizon, with a median window of eight hours. Same feature type, two very different horizons: bound your query window rather than trusting the field to mean the same thing everywhere. What these records do carry, and live feeds lack, is &lt;strong&gt;a projected start and end date&lt;/strong&gt;. They also carry the property that makes them awkward to consume: &lt;em&gt;those dates are estimates, and they move.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Dates Come From
&lt;/h2&gt;

&lt;p&gt;Road511 now normalizes planned-construction records from nine states and provinces into one &lt;code&gt;future_construction&lt;/code&gt; feature type — &lt;strong&gt;2,161 projects&lt;/strong&gt; as of today, each with whatever schedule the agency published. Virginia’s upcoming-closures layer is the deepest (927 records), with Delaware’s project portal close behind (774), then New Jersey’s STIP project locations (247), West Virginia’s project records (162) and North Carolina’s future-closures feed (42), with Idaho, New Brunswick, Nevada, and Prince Edward Island contributing the rest. West Virginia also publishes planned work as events carrying &lt;code&gt;status=planned&lt;/code&gt; — another shape, same question.&lt;/p&gt;

&lt;p&gt;Treat that total as a reading, not a constant. Virginia’s layer is a rolling window that drops records once their dates pass: it stood at 2,176 in early July and 927 today, which is the feed working as designed rather than coverage being lost. Feeds also go quiet — Manitoba, Newfoundland, Nevada and Prince Edward Island are each returning nothing at the moment, so on any given day the number of jurisdictions actually publishing is smaller than the number we can parse. Query it rather than quoting it.&lt;/p&gt;

&lt;p&gt;Nine jurisdictions is not fifty, and we won’t pretend otherwise. But it’s the corridors where DOTs actually publish forward-looking schedules as machine-readable data, and each one arrives on its own schema, its own date format, and its own idea of what “start” means. Folding them into a single feature type with a single pair of timestamps is the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asking the Question Directly
&lt;/h2&gt;

&lt;p&gt;Once every project shares one schema, “what will be under construction on this corridor in August” stops being a spreadsheet exercise and becomes a query. The &lt;code&gt;/features&lt;/code&gt; endpoint now takes four scheduled-window filters — &lt;code&gt;starts_after&lt;/code&gt;, &lt;code&gt;starts_before&lt;/code&gt;, &lt;code&gt;ends_after&lt;/code&gt;, &lt;code&gt;ends_before&lt;/code&gt; — each accepting an RFC3339 timestamp or a plain &lt;code&gt;YYYY-MM-DD&lt;/code&gt; date.&lt;/p&gt;

&lt;p&gt;Bound the window from both ends and you get overlap: every project whose scheduled dates intersect the period you care about, whether it starts inside the window, ends inside it, or spans straight through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/features?
  type=future_construction&amp;amp;
  jurisdiction=VA&amp;amp;
  starts_before=2026-09-01&amp;amp;
  ends_after=2026-08-01

{
  "data": [
    {
      "id": "VA-fcon-WZNO4585395-12252025-298",
      "source": "VA",
      "jurisdiction": "VA",
      "feature_type": "future_construction",
      "name": "On I-66W Express Lanes from mile marker 65 to mile marker 43",
      "road_name": "On I-66W Express Lanes from mile marker 65 to mile marker 43",
      "latitude": 38.886177,
      "longitude": -77.221801,
      "is_active": true,
      "estimated_start_time": "2026-08-12T02:00:00Z",
      "estimated_end_time": "2026-08-12T09:00:00Z",
      "last_updated": "2026-08-11T08:43:55Z"
    }
  ],
  "total": 801,
  "limit": 100,
  "offset": 0,
  "has_more": true
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole ask: one call, one corridor, one month, every project that will be in the way. Add &lt;code&gt;bbox&lt;/code&gt; or &lt;code&gt;lat&lt;/code&gt;/&lt;code&gt;lng&lt;/code&gt;/&lt;code&gt;radius_km&lt;/code&gt; to narrow it to the geography the load actually travels, or hit &lt;code&gt;/features/geojson&lt;/code&gt; to drop the same result straight onto a map.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two caveats on that example.&lt;/strong&gt; &lt;code&gt;total&lt;/code&gt; is a live reading, not a constant — run the query rather than quoting the number. And Virginia is the short-horizon case described above: it publishes overnight lane work about three days out, so the same query aimed at a month further ahead returns almost nothing. That is a property of the agency, not of the API. For a long-horizon pipeline, run it against &lt;code&gt;jurisdiction=DE&lt;/code&gt;, whose records carry start dates years out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two details worth knowing
&lt;/h3&gt;

&lt;p&gt;The filters match on the &lt;strong&gt;planned&lt;/strong&gt; dates where an agency publishes them and fall back to actual dates where it doesn’t — so a source that reports only a real start time stays queryable on the same parameters, and you don’t need to know which kind of source you’re talking to.&lt;/p&gt;

&lt;p&gt;And a project with a known start but no published end is treated as &lt;strong&gt;open-ended&lt;/strong&gt;: it matches &lt;code&gt;ends_after&lt;/code&gt;, because a job with no end date certainly might still be running in August. It never matches &lt;code&gt;ends_before&lt;/code&gt;, because nothing about it promises it will be finished. A record with no dates at all — a project the agency has announced but not scheduled — drops out of window queries entirely rather than quietly padding your results. Those two rules are the difference between a date filter you can plan against and one that lies to you at the edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dates Move. That’s the Interesting Part.
&lt;/h2&gt;

&lt;p&gt;A planned start date is a forecast, and forecasts slip. Utility work runs long, a bridge job waits on steel, the whole thing gets pushed a season. Any planner who has been burned once knows not to treat &lt;code&gt;estimated_start_time&lt;/code&gt; as gospel — and the useful response is not to distrust the data but to &lt;em&gt;watch it change&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So we keep the history. Every time an agency revises a project’s projected start or end, the change is recorded — old value, new value, when we saw it — and it’s queryable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/analytics/feature-history?
  change_type=estimated_start_time_change
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turns a static schedule into a signal. A project that has been rescheduled four times is telling you something a single date field never could. At the jurisdiction level, &lt;code&gt;/analytics/scorecard&lt;/code&gt; asks the same question of a wider pool — how far does an agency move the end dates it publishes? — so you can calibrate how much slack a given DOT’s published dates historically need. It rolls up event-modelled roadwork rather than the &lt;code&gt;future_construction&lt;/code&gt; records above, and it reports only the jobs whose revisions we can still see, so read the two as complementary views of the same habit rather than one number. Both live behind the analytics tier (Pro and up); the &lt;code&gt;future_construction&lt;/code&gt; records themselves are available on every plan, free trial included.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Build With It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permit and oversize-load planning&lt;/strong&gt; — check the corridor for the delivery week, not for today, and catch the lane restriction that lands between quote and haul.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal lane and contract pricing&lt;/strong&gt; — know which corridors carry scheduled disruption during the months you’re quoting, before the rate is locked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detour design that doesn’t collide&lt;/strong&gt; — route around a work zone without routing into the one starting next week two miles over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule-drift monitoring&lt;/strong&gt; — watch the projects on your key corridors and get told when the dates move, instead of rediscovering it on arrival.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planning views that pair with the present&lt;/strong&gt; — planned construction beside live work zones, bridge clearances, and &lt;a href="https://road511.com/blog/spring-load-limits-api-trucking.html" rel="noopener noreferrer"&gt;seasonal load limits&lt;/a&gt; in one schema, on one corridor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Provenance, and the Honest Limits
&lt;/h2&gt;

&lt;p&gt;These records come from the DOTs’ own project pipelines — official, attributed, and refreshed on our poll rather than transcribed by hand. The limits are real and worth stating plainly. Coverage is nine states and provinces, not the whole map, because forward-looking schedules are the least consistently published data a DOT produces — and as noted above, on any given day fewer than nine are actually returning records. Every date is an estimate, published by the agency and subject to revision without notice; that is a property of construction, not of the feed, and the change log exists precisely because of it. Detail varies — some sources give a full project description and route, others give a name and a pair of dates. And a planned closure is a plan: it tells you what an agency currently intends, which is the best information that exists three weeks out, and still not a promise.&lt;/p&gt;

&lt;p&gt;The job we take on is keeping nine project pipelines parsed, normalized, and current as they drift — so it isn’t yours. This is the same integration tax we described in &lt;a href="https://road511.com/blog/north-american-road-data-challenges.html" rel="noopener noreferrer"&gt;the five road-data problems every NA fleet team hits&lt;/a&gt;, applied to the one dataset that lets you get ahead of the problem instead of reacting to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — the &lt;code&gt;features&lt;/code&gt; surface, including &lt;code&gt;future_construction&lt;/code&gt; and the scheduled-window filters&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — see planned construction alongside live conditions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/planned-construction-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=planned-construction-api&amp;amp;utm_content=dev-07" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>logistics</category>
      <category>trucking</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your ETA Assumes the Border Takes an Hour. It Can Take Four Days.</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Sat, 08 Aug 2026 08:19:40 +0000</pubDate>
      <link>https://dev.to/road511/your-eta-assumes-the-border-takes-an-hour-it-can-take-four-days-4g3</link>
      <guid>https://dev.to/road511/your-eta-assumes-the-border-takes-an-hour-it-can-take-four-days-4g3</guid>
      <description>&lt;p&gt;Inside Schengen, a border is a road sign. You cross it at motorway speed and your ETA never notices. Twenty years of that has trained every routing engine, every TMS, and every dispatcher's intuition to treat a European frontier as a rounding error.&lt;/p&gt;

&lt;p&gt;Then a load runs to the EU's &lt;em&gt;external&lt;/em&gt; frontier, and the rounding error becomes the trip.&lt;/p&gt;

&lt;p&gt;On the morning of 16 July 2026, the loaded-truck queue at Yahodyn–Dorohusk — the main Ukraine–Poland freight crossing — was reporting a wait of &lt;strong&gt;5,964 minutes&lt;/strong&gt;. That is &lt;strong&gt;four days and three hours&lt;/strong&gt;, with &lt;strong&gt;1,006 trucks&lt;/strong&gt; physically in the queue. The queue for empty trucks at the same crossing was worse: &lt;strong&gt;6,555 minutes&lt;/strong&gt;, or four days and thirteen hours, with 1,334 trucks waiting.&lt;/p&gt;

&lt;p&gt;No amount of clever road-network routing tells you that. It isn't a traffic condition, it isn't congestion, and it will not resolve in twenty minutes. It is a queue with a length, and the only way to know about it is to read the queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Average Border Wait" Is a Useless Number
&lt;/h2&gt;

&lt;p&gt;The instinct is to reach for an average and pad the ETA. The live data says don't bother.&lt;/p&gt;

&lt;p&gt;Across the 38 truck checkpoints on Ukraine's EU-facing frontier that morning, the distribution wasn't a bell curve. It was two clusters and nothing in between: &lt;strong&gt;21 checkpoints reported a wait of exactly zero&lt;/strong&gt;, and the rest ran from about ten hours to four and a half days. There was essentially no middle.&lt;/p&gt;

&lt;p&gt;That shape is the whole planning problem. A border crossing is either flowing — in which case it costs you nothing and needs no padding — or it is a multi-day parking lot. An average across those two states describes no crossing that actually exists, and a fixed buffer is either wasted margin or nowhere near enough. The only useful question is binary and current: &lt;em&gt;is this specific crossing, for this kind of truck, moving right now?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Queue Is Published. It Just Isn't in Your Stack.
&lt;/h2&gt;

&lt;p&gt;Ukraine runs an electronic queue system for border crossings — єЧерга (eCherha) — operated by the State Customs Service. Trucks book and hold a place in it, and it publishes the live state of every checkpoint: how long the queue is taking, and how many vehicles are in it. The underlying dataset is open data, published under CC BY 4.0.&lt;/p&gt;

&lt;p&gt;NAPSPAN normalizes it into the same &lt;code&gt;border_crossings&lt;/code&gt; feature type that carries the CBP and CBSA land-border waits on the North American side, so it answers to the same &lt;code&gt;/features&lt;/code&gt; call as everything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.napspan.com/api/v1/features?type=border_crossings&amp;amp;jurisdiction=UKR"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echerga-truck-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UKR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UKR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"feature_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ягодин – Дорогуськ (для вантажівок ≥ 7,5 тонн)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;51.1880897&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;23.8105033&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source_feed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echerga"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vehicle_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"truck"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"queue_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dynamic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"wait_time_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5964&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vehicles_in_queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1006&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"neighbor_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Польща"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"is_paused"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"last_updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-16T09:40:00Z"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"has_more"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole frontier is in there. That call returns 68 queues facing five neighbours — Poland, Slovakia, Hungary, Romania, and Moldova — each with a live wait and a live queue depth, refreshed on a ten-minute poll. &lt;strong&gt;38 of them are truck queues&lt;/strong&gt;; the rest are the coach queues the same system publishes. &lt;code&gt;vehicle_type&lt;/code&gt; tells them apart, and it's the field to filter on — a coach queue says nothing about your freight lane.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part That Changes Dispatch: It Rides the Route
&lt;/h2&gt;

&lt;p&gt;Querying the frontier by jurisdiction is the manual path. The one that changes a dispatcher's day is that &lt;strong&gt;the queue comes back attached to the route.&lt;/strong&gt; Send a normal truck routing request to &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt;, and any border crossing on the corridor arrives in the &lt;code&gt;warnings[]&lt;/code&gt; channel — no second call, no spatial join on your side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"border_crossing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ягодин – Дорогуськ (для вантажівок ≥ 7,5 тонн)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Commercial border wait ~4 d 3 h. 1006 trucks in queue."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-16T09:40:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"distance_along_route_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;288000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"projected_arrival_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-16T14:05:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UKR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Point"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;23.8105033&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;51.1880897&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"commercial_wait_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5964&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vehicles_in_queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1006&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few deliberate choices in that object are worth calling out, because they are what make it usable rather than merely present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The wait reads like a wait.&lt;/strong&gt; &lt;code&gt;description&lt;/code&gt; renders as &lt;code&gt;4 d 3 h&lt;/code&gt;, not &lt;code&gt;~5964 min&lt;/code&gt;. Nobody can act on 5,964 minutes. The machine-readable &lt;code&gt;commercial_wait_minutes&lt;/code&gt; stays in minutes so your own thresholds keep working — the humanising is presentation, not data loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue depth comes with it.&lt;/strong&gt; &lt;code&gt;vehicles_in_queue&lt;/code&gt; is the figure that makes the wait concrete. "Four days" is abstract; "a thousand trucks are in front of you" is a decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity is banded for a truck.&lt;/strong&gt; &lt;code&gt;info&lt;/code&gt; under 15 minutes, &lt;code&gt;warning&lt;/code&gt; from 15, &lt;code&gt;critical&lt;/code&gt; past 45 or whenever the crossing is closed. Set &lt;code&gt;min_severity: "warning"&lt;/code&gt; and a flowing border stays silent while a jammed one shouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the truck queue counts.&lt;/strong&gt; The feed publishes each checkpoint once per vehicle type. A ninety-minute coach queue at the same crossing never bands your freight route — we key severity on the truck queue alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get the crossing before the driver does.&lt;/strong&gt; &lt;code&gt;distance_along_route_m&lt;/code&gt; and &lt;code&gt;projected_arrival_time&lt;/code&gt; place it on the corridor, so this is a decision made at dispatch — reroute to a quieter checkpoint, re-sequence the load, warn the customer — not a surprise found at hour ninety.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It rides the same request as the corridor's &lt;a href="https://napspan.com/blog/cross-border-truck-restrictions-vms.html" rel="noopener noreferrer"&gt;truck restrictions and VMS signs&lt;/a&gt;, its &lt;a href="https://napspan.com/blog/toll-cost-routing-api.html" rel="noopener noreferrer"&gt;toll costs&lt;/a&gt;, and &lt;a href="https://napspan.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;561/2006 break planning&lt;/a&gt;. One call returns a drivable truck route, the rules that would stop it, where the driver must legally rest, what it costs — and now how long the frontier will hold it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Expect From the Data
&lt;/h2&gt;

&lt;p&gt;A few honest notes, so the coverage behaves the way you expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is the external frontier, not every EU border.&lt;/strong&gt; Intra-Schengen crossings do not queue, so there is nothing to report and no warning to give. The live queue layer covers Ukraine's EU-facing checkpoints, which is where the EU's freight border problem actually lives. Other external frontiers do not yet have an equivalent live feed wired — we will say so plainly rather than pad the map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One physical crossing publishes several queues.&lt;/strong&gt; Yahodyn–Dorohusk reports loaded trucks, empty trucks, and certain cargo categories as separate rows at the same coordinates, and they diverge — on 16 July the empty-truck queue ran ten hours longer than the loaded one. Read the checkpoint name; don't assume one number per border post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paused checkpoints drop out.&lt;/strong&gt; When the operator suspends a queue, the feed flags it and we mark the crossing inactive, so it stops generating warnings rather than reporting a stale wait as live. Ten of the 38 were paused that morning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some checkpoints are scheduled, not live.&lt;/strong&gt; Where a checkpoint runs on booked slots instead of a live queue, there is no wait to report — it stays &lt;code&gt;info&lt;/code&gt; and we publish no invented number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a planning input.&lt;/strong&gt; The queue is authoritative about the queue. It is not a promise about customs, paperwork, or what the officer at the window decides.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Common Thread
&lt;/h2&gt;

&lt;p&gt;This is the same shape as the &lt;a href="https://napspan.com/blog/european-road-data-challenges.html" rel="noopener noreferrer"&gt;five road-data problems every cross-border European fleet hits&lt;/a&gt;: the information exists, a public authority publishes it, and it is in a national format that nobody's routing stack speaks. Solving that once per country is a permanent maintenance commitment that competes with the product you actually sell.&lt;/p&gt;

&lt;p&gt;NAPSPAN does that aggregation once. The Ukrainian electronic queue arrives in the same &lt;code&gt;border_crossings&lt;/code&gt; schema as a US CBP lane wait, filterable by jurisdiction, attached to your route on the same request as the tolls and the rest breaks, served from Europe, and licence-classified before it ships — this feed under CC BY 4.0, credited on every record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://napspan.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — &lt;code&gt;/features&lt;/code&gt; with &lt;code&gt;type=border_crossings&lt;/code&gt;, plus &lt;code&gt;POST /routing/route&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.napspan.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — the frontier, as it stands right now&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.napspan.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://napspan.com/blog/eu-border-queue-wait-times.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=eu-border-queue-wait-times&amp;amp;utm_content=dev-09" rel="noopener noreferrer"&gt;napspan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logistics</category>
      <category>eu</category>
      <category>api</category>
      <category>trucking</category>
    </item>
    <item>
      <title>Truck Parking Availability on the Route, Before the Clock Runs Out</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Fri, 24 Jul 2026 07:31:59 +0000</pubDate>
      <link>https://dev.to/road511/truck-parking-availability-on-the-route-before-the-clock-runs-out-17n4</link>
      <guid>https://dev.to/road511/truck-parking-availability-on-the-route-before-the-clock-runs-out-17n4</guid>
      <description>&lt;p&gt;It's 2am and a driver's 11-hour clock has maybe 30 minutes left on it. The rest area at the next exit is full — trucks are already lined up on the on-ramp shoulder. The next public lot is 40 miles ahead, which the clock won't cover, and nobody knows whether &lt;em&gt;it&lt;/em&gt; has room either. So the driver does what tens of thousands do every night: parks somewhere they shouldn't, or keeps rolling past the legal limit. Truck parking consistently ranks among the trucking industry's top concerns year after year, and the reason it stays unsolved at the cab level is simple — &lt;strong&gt;a legal break is worthless if there's nowhere to stop, and the driver finds out too late to do anything about it.&lt;/strong&gt; That last part is the part software can fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a Space Is a Planning Problem, Not a Luck Problem
&lt;/h2&gt;

&lt;p&gt;The shortage is real and structural — there are far more trucks needing overnight rest than there are legal spaces for them. But a huge share of the daily pain isn't the raw shortage; it's the &lt;em&gt;information gap&lt;/em&gt;. The driver can't see which lots are full until pulling in, and the dispatcher planning the load has no parking picture at all. A stop that looks fine on a map at 9am dispatch is a gravel shoulder by the time the truck arrives at midnight.&lt;/p&gt;

&lt;p&gt;Turning that into a planning decision needs two things the route already implies: &lt;strong&gt;where the parking is&lt;/strong&gt; along the corridor, and &lt;strong&gt;how much of it is actually open&lt;/strong&gt; when the truck will be there. The first has existed for years. The second is finally becoming available — in pieces — and that's the part that changes the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Exists — in Two Forms
&lt;/h2&gt;

&lt;p&gt;Truck-parking data comes from two complementary public sources, and they answer different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The national inventory — where parking exists.&lt;/strong&gt; A federal facilities dataset catalogs public rest areas and known truck-parking sites across the country, with locations, capacity, and basic attributes. This is your map of &lt;em&gt;where&lt;/em&gt; a truck can legally stop — comprehensive in coverage, but static: it tells you a site has 60 spaces, not how many are free right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live availability — how many spaces are open.&lt;/strong&gt; A growing number of state DOTs run &lt;strong&gt;Truck Parking Information Management Systems (TPIMS)&lt;/strong&gt; — the in-pavement sensors and roadside signs you've seen reading "REST AREA — 12 SPACES." Those same counts are published as data. Coverage is still a handful of states rather than the whole map, but where a site is instrumented, you get a live, refreshing count of open spaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch is the one that shows up everywhere in this domain: these are separate feeds in separate formats from separate agencies, each on its own schema and refresh cadence — the same per-source integration tax we wrote about in &lt;a href="https://road511.com/blog/north-american-road-data-challenges.html" rel="noopener noreferrer"&gt;the five road-data problems every NA fleet team knows&lt;/a&gt;. Stitched together and normalized, though, they become one answer: the full map of where to stop, with live counts layered on wherever a state publishes them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Parking Stop Needs to Tell You
&lt;/h2&gt;

&lt;p&gt;A parking feature is only useful to a planner when it answers the operational questions, not just "there's a rest area here":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where, and which side&lt;/strong&gt; — the exact location and the direction of travel it serves, so a northbound truck isn't routed to a southbound-only lot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What kind, and how big&lt;/strong&gt; — public rest area versus known truck stop, and the total truck-space capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many are open right now&lt;/strong&gt; — the live available-space count where the site is instrumented, with the timestamp of the last update so stale data is obvious rather than misleading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's there&lt;/strong&gt; — amenities that decide whether a stop is viable for a 10-hour reset versus a quick 30-minute break.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting It on the Corridor
&lt;/h2&gt;

&lt;p&gt;The operational need isn't "a database of rest areas." It's "show me the truck parking &lt;em&gt;on this route&lt;/em&gt;, with whatever live counts exist, before the driver commits to a stop." Once the inventory and the TPIMS feeds are normalized into one schema, &lt;code&gt;/truck/corridor&lt;/code&gt; buffers the straight line between two points and returns every truck-related feature that intersects it — parking among them — instead of a fan-out across federal and state sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/truck/corridor?
  from_lat=41.88&amp;amp;from_lng=-87.63&amp;amp;
  to_lat=39.10&amp;amp;to_lng=-94.58&amp;amp;
  buffer_km=5

{
  "features": [
    {
      "id": "tpims-IL00055IS002210",
      "feature_type": "truck_parking",
      "name": "I-55 NB Rest Area (Lincoln)",
      "jurisdiction": "IL",
      "road_name": "I-55 NB",
      "latitude": 40.71, "longitude": -89.62,
      "properties": {
        "capacity": 62,
        "available_spaces": 9,
        "occupancy_pct": 85,
        "data_updated": "2026-06-26T06:18:00Z",
        "amenities": ["Vending Machines"],
        "open": true,
        "_distance_km": "1.20"
      }
    },
    {
      "id": "BTS-tp-4471",
      "feature_type": "truck_parking",
      "name": "US-67 Truck Parking",
      "jurisdiction": "IL",
      "road_name": "US-67",
      "latitude": 39.80, "longitude": -90.20,
      "properties": {
        "capacity": 40,
        "county": "Morgan",
        "milepost": "12.4",
        "truck_parking": true,
        "_distance_km": "3.80"
      }
    }
  ],
  "total": 2
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call, the Chicago–Kansas City corridor, every known parking site on it, each stamped with &lt;code&gt;_distance_km&lt;/code&gt; off the line. Where a state runs TPIMS, the site carries a live &lt;code&gt;available_spaces&lt;/code&gt; / &lt;code&gt;occupancy_pct&lt;/code&gt; and a &lt;code&gt;data_updated&lt;/code&gt; timestamp; where it doesn't, the site still shows up from the national BTS inventory with &lt;code&gt;capacity&lt;/code&gt; only and no &lt;code&gt;available_spaces&lt;/code&gt; key at all — an honest "we know it's here, we don't have a live count" instead of a false promise. That distinction is the whole point: a planner can prefer instrumented sites near the deadline and fall back to known lots everywhere else. (&lt;code&gt;/truck/corridor&lt;/code&gt; is a paid-plan endpoint; the same features are on &lt;code&gt;/features?type=truck_parking&amp;amp;bbox=…&lt;/code&gt; for an area query.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Parking Meets the HOS Clock
&lt;/h2&gt;

&lt;p&gt;Parking and hours-of-service are the same problem looked at from two ends. The clock decides &lt;em&gt;when&lt;/em&gt; the truck has to stop; the parking data decides &lt;em&gt;whether it can&lt;/em&gt;. On its own, a list of nearby lots is useful; tied to the driver's remaining hours, it becomes a go/no-go answer.&lt;/p&gt;

&lt;p&gt;That's exactly how it connects to &lt;a href="https://road511.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;HOS break planning on the route&lt;/a&gt;: send the driver's clock with the route, and each required break and drive-limit stop comes back with the truck parking reachable &lt;em&gt;before&lt;/em&gt; the deadline — and a &lt;code&gt;feasible&lt;/code&gt; flag when nothing legal is in range. Layer live availability on top and "reachable" sharpens into "reachable and likely to have room," so dispatch can move the stop earlier while there's still slack in the clock, instead of discovering the lot is full when there's no clock left to spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Build With It
&lt;/h2&gt;

&lt;p&gt;With truck parking in the same schema as everything else on the route, the integrations get short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dispatch that plans the overnight, not just the delivery&lt;/strong&gt; — place the rest stop at load planning, with capacity and live counts in view, not at 2am.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Driver alerts as the clock winds down&lt;/strong&gt; — surface the next few viable lots ahead, instrumented ones flagged, while there are still hours to reach them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest ETAs&lt;/strong&gt; — a reset has to happen somewhere; planning the actual stop makes the arrival estimate real instead of optimistic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corridor risk and compliance views&lt;/strong&gt; — parking alongside bridge clearances, work zones, and incidents in one layer (see &lt;a href="https://road511.com/blog/truck-routing-hazard-warnings.html" rel="noopener noreferrer"&gt;truck routing with corridor hazard warnings&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Provenance, and the Honest Limits
&lt;/h2&gt;

&lt;p&gt;Parking data comes from official sources — the federal facilities inventory and state DOT TPIMS feeds — which is what makes it authoritative rather than crowd-guessed. We don't overstate it. Live counts exist only where a state has instrumented a site, so most of the map is still inventory-only; capacity and amenity detail vary by source; and a live count is only as fresh as the feed behind it, which is why every count carries its update timestamp. This is &lt;em&gt;information&lt;/em&gt;, not a reservation — the public feeds tell you what's open, not hold a space for you; booking lives in separate private systems. The job we take on is keeping the federal inventory and each state's TPIMS feed parsed, current, and in one shape as they grow and drift — so it's not yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — the &lt;code&gt;features&lt;/code&gt; and corridor surface, including &lt;code&gt;truck_parking&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — see truck-parking sites and live counts where states publish them&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The next legal stop, with room — before the clock runs out
&lt;/h3&gt;

&lt;p&gt;The national truck-parking inventory plus live space counts where states publish them, normalized into one schema and queryable along any corridor — and tied to the HOS clock so the break is planned, not gambled. Free 14-day trial. No credit card. &lt;strong&gt;&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Get a free API key&lt;/a&gt;&lt;/strong&gt; or &lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;read the docs&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/truck-parking-availability-api.html" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>trucking</category>
      <category>api</category>
      <category>logistics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Five Road-Data Problems Every Cross-Border European Fleet Hits</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Sun, 12 Jul 2026 07:18:21 +0000</pubDate>
      <link>https://dev.to/road511/five-road-data-problems-every-cross-border-european-fleet-hits-2i3</link>
      <guid>https://dev.to/road511/five-road-data-problems-every-cross-border-european-fleet-hits-2i3</guid>
      <description>&lt;p&gt;If you build routing, dispatch, or telematics software for European trucking, the routing algorithm is rarely what slows the roadmap down. The road data is. A truck plan from Rotterdam to Bucharest crosses a dozen jurisdictions, and each one publishes what's happening on its roads — incidents, tolls, restrictions, charging — its own way, on its own schedule, under its own rules. Below are the five problems that come up in nearly every conversation we have with European fleet and logistics-software teams. None are exotic. All of them quietly burn engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Twenty-Seven Countries, Twenty-Seven Access Points
&lt;/h2&gt;

&lt;p&gt;The EU ITS Directive did get every Member State to stand up a National Access Point (NAP) publishing road data, mostly as DATEX II. That sounds like a solved problem until you actually connect to a second country. The standard leaves room, and the NAPs use all of it — different DATEX profiles and versions, different authentication, different update cadences, different ideas of what a given field means. And plenty of operationally useful data never travels as DATEX II at all: it sits on municipal open-data portals or in national feeds that predate the plumbing.&lt;/p&gt;

&lt;p&gt;So a team that wants genuine continental coverage ends up writing and &lt;em&gt;maintaining&lt;/em&gt; a separate integration per country, then babysitting all of them as feeds quietly change shape. This is the problem that eats the most time, because it never finishes. We wrote up &lt;a href="https://napspan.com/blog/datex-ii-normalization-european-naps.html" rel="noopener noreferrer"&gt;how we normalize DATEX II across the European NAPs&lt;/a&gt; and &lt;a href="https://napspan.com/blog/open-data-beyond-datex-ii.html" rel="noopener noreferrer"&gt;how the non-DATEX sources fold into the same schema&lt;/a&gt; — the short version is that the per-country mess becomes one consistent &lt;code&gt;events&lt;/code&gt; and &lt;code&gt;features&lt;/code&gt; contract, and keeping the adapters alive becomes our problem instead of yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Toll Bill Changes at Every Border
&lt;/h2&gt;

&lt;p&gt;There is no single European toll. There are vignettes, distance-based schemes, per-axle HGV charges, urban congestion fees, and tunnel and bridge tolls — each country with its own model, its own vehicle classes, and its own emission-class surcharges. A route that looks cheapest on distance can be the most expensive once the tolls land, and the difference between a 5-axle Euro VI rig and a smaller van is real money on every leg.&lt;/p&gt;

&lt;p&gt;For a planner, an estimate that ignores vehicle class is an estimate that's wrong at invoice time. For a software team, pricing a cross-border route by hand means modelling a dozen national toll systems and keeping them current as rates change. We pulled this together so a route comes back with a vehicle-class-aware toll breakdown per country — see &lt;a href="https://napspan.com/blog/toll-cost-routing-api.html" rel="noopener noreferrer"&gt;vehicle-class-aware toll costs across Europe&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Where the Clock Runs Out — and Whether There's Parking
&lt;/h2&gt;

&lt;p&gt;EU Regulation 561/2006 driving-time rules and the chronic shortage of secure truck parking are really one problem wearing two hats. Knowing a driver will hit the daily driving limit at 18:40 is only half of it. The half that strands a truck — or forces an unsafe roadside stop — is whether there's a legal, reachable place to take the break when the clock runs out, and that answer needs both the driving-time projection &lt;em&gt;and&lt;/em&gt; live parking data on the same corridor.&lt;/p&gt;

&lt;p&gt;Most teams solve the first half and discover the second half at the lay-by. We tied them together: send the driver's clock with the route and get back every required break, each paired with the parking reachable before the deadline and a &lt;code&gt;feasible&lt;/code&gt; flag when none is. That's the subject of &lt;a href="https://napspan.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;break planning under EU 561/2006&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Ban You Didn't Know About Until the Border
&lt;/h2&gt;

&lt;p&gt;Cross-border trucking runs on a patchwork of national restrictions that no consumer map knows about: weekend and night driving bans, sectoral and sector-specific bans on Alpine corridors, low-emission zones in city centres, country-by-country weight and dimension limits, and seasonal restrictions. A plan that's legal in one country can be illegal across the line at the same hour — and the penalty for finding out the hard way is a fine, a forced wait, or a turned-back load.&lt;/p&gt;

&lt;p&gt;The operational need isn't "a list of every rule in Europe." It's "tell me about the restrictions and variable-message signs &lt;em&gt;on this corridor&lt;/em&gt;, before the driver gets there" — restrictions and signage from the official national feeds, normalized into the same queryable &lt;code&gt;features&lt;/code&gt; the rest of the API speaks, filterable by country and bounding box. One schema for a German night ban and an Austrian sectoral ban means you check one place instead of twenty-seven.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Charging an Electrifying Fleet, Across the Gaps
&lt;/h2&gt;

&lt;p&gt;As fleets electrify, "where can this vehicle actually charge on the corridor" becomes a routing input, not a nice-to-have. The EU's AFIR regulation obliges charge-point operators to publish charging data through the NAPs, and where a Member State carries a native EV feed we serve it. But AFIR rollout is uneven, and several countries don't expose a mature EV feed yet — so most of our EV coverage comes from an openly-licensed registry rather than from live per-connector telemetry.&lt;/p&gt;

&lt;p&gt;That registry gives you coordinates, connector standards, and power ratings, and where a source publishes its own last-checked time we pass it through as &lt;code&gt;properties.source_updated_at&lt;/code&gt; so you can judge how fresh an entry is — typically days to months, because a community registry is not live telemetry. See &lt;a href="https://napspan.com/blog/open-data-beyond-datex-ii.html" rel="noopener noreferrer"&gt;folding open data into one European API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Thread
&lt;/h2&gt;

&lt;p&gt;Every one of these is the same shape of problem: the data exists, but it's fragmented across national authorities, published in incompatible formats, governed by different licenses, and only useful once it's joined to a route and kept fresh. Each one is solvable in-house — and each one, solved in-house, becomes a permanent maintenance commitment that competes with the product you actually sell.&lt;/p&gt;

&lt;p&gt;That's the entire premise of NAPSPAN: do that aggregation once, for everyone, and hand you one normalized API — events, features, tolls, restrictions, charging, routing with corridor hazards — instead of twenty-seven integrations to keep alive. The data is served from Europe, every source is license-classified before it ships, and you spend your engineering time on the part that makes your product yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://napspan.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — &lt;code&gt;events&lt;/code&gt;, &lt;code&gt;features&lt;/code&gt;, routing, and the &lt;code&gt;?jurisdiction=&lt;/code&gt; / &lt;code&gt;?type=&lt;/code&gt; filters in one reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.napspan.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — see the normalized data across the European NAPs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.napspan.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;One API instead of twenty-seven integrations.&lt;/strong&gt; Continental coverage — incidents, tolls, truck restrictions, EV charging, and routing — normalized into one GeoJSON schema and served from Europe. Free 14-day trial. No card. &lt;a href="https://portal.napspan.com/signup" rel="noopener noreferrer"&gt;Get a free API key&lt;/a&gt; or &lt;a href="https://napspan.com/docs.html" rel="noopener noreferrer"&gt;read the docs&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://napspan.com/blog/european-road-data-challenges.html" rel="noopener noreferrer"&gt;napspan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logistics</category>
      <category>eu</category>
      <category>api</category>
      <category>trucking</category>
    </item>
    <item>
      <title>Five Road-Data Problems Every North American Fleet Team Knows Too Well</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/road511/five-road-data-problems-every-north-american-fleet-team-knows-too-well-nle</link>
      <guid>https://dev.to/road511/five-road-data-problems-every-north-american-fleet-team-knows-too-well-nle</guid>
      <description>&lt;p&gt;If you build routing, dispatch, or telematics software for North American trucking, your roadmap has a recurring tax line on it — road data. Not the routing algorithm, not the UI: the unglamorous job of knowing what's actually happening on the pavement, in fifty states and a dozen provinces that each run their own system their own way. Below are the five problems that come up in nearly every conversation we have with fleet and logistics-software teams. None of them are exotic. All of them quietly burn engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fifty States, Fifty Dialects
&lt;/h2&gt;

&lt;p&gt;There is no single "US traffic API." There are dozens of state and provincial 511 systems, and they agree on almost nothing — different formats (some JSON, some XML, some an ArcGIS layer, some a scraped map endpoint), different field names for the same concept, different authentication, different refresh rates, different ideas of what "severity" even means. A team that wants national coverage ends up writing and &lt;em&gt;maintaining&lt;/em&gt; a separate integration per jurisdiction, then babysitting all of them as feeds silently change shape.&lt;/p&gt;

&lt;p&gt;This is the problem that eats the most time, because it never finishes. A feed that worked in March drops a field in September; a state migrates its portal and the old endpoint 404s. We wrote up &lt;a href="https://road511.com/blog/normalizing-511-traffic-apis.html" rel="noopener noreferrer"&gt;how we normalize 30+ of these systems into one schema&lt;/a&gt; — the short version is that the per-jurisdiction mess becomes one consistent &lt;code&gt;events&lt;/code&gt; and &lt;code&gt;features&lt;/code&gt; contract, and keeping the adapters alive becomes our problem instead of yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Bridge Your Truck Doesn't Fit Under
&lt;/h2&gt;

&lt;p&gt;A consumer nav app routes a 13′6″ trailer under a 12′8″ bridge without a second thought, because it has no idea a truck is driving. Low-clearance strikes are expensive, dangerous, and almost entirely preventable with the right data on the route — but that data is scattered across state bridge inventories and the federal National Bridge Inventory, in formats never meant for real-time routing.&lt;/p&gt;

&lt;p&gt;The operational need isn't "a database of bridges." It's "tell me about the low ones &lt;em&gt;on this corridor&lt;/em&gt;, before the driver gets there." That's a join between clearance data and the route geometry, and it's exactly the kind of thing teams underestimate until the first claim comes in. More on that in &lt;a href="https://road511.com/blog/bridge-clearance-api-fleet-routing.html" rel="noopener noreferrer"&gt;bridge clearances for fleet routing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Limits That Appear Every Spring
&lt;/h2&gt;

&lt;p&gt;Seasonal weight restrictions — spring thaw load limits, frost laws — are one of the most under-served data problems in North American trucking. They're posted by states and counties on their own schedules, in their own formats, sometimes as a PDF, and they change as the ground does. A load that's legal one week is over the limit the next, on the same road.&lt;/p&gt;

&lt;p&gt;For a planner, the cost of missing one is a fine, a forced reroute, or a refused load. For a software team, the cost is trying to track a hundred separate posting authorities by hand. We pulled this together so the restriction shows up as structured, queryable data alongside everything else — see &lt;a href="https://road511.com/blog/spring-load-limits-api-trucking.html" rel="noopener noreferrer"&gt;spring load limits as an API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Where the Clock Runs Out — and Whether There's Parking
&lt;/h2&gt;

&lt;p&gt;Hours-of-Service compliance and the chronic truck-parking shortage are really one problem wearing two hats. Knowing a driver will hit the 11-hour driving limit at 6:40 PM is only half of it. The half that strands a truck is whether there's a legal place to stop when the clock runs out — and that answer needs both the HOS projection &lt;em&gt;and&lt;/em&gt; live parking data on the same corridor.&lt;/p&gt;

&lt;p&gt;Most teams solve the first half and discover the second half at the roadside. We tied them together: send the driver's clock with the route and get back every required break and stop, each paired with the parking reachable before the deadline and a &lt;code&gt;feasible&lt;/code&gt; flag when none is. That's the subject of &lt;a href="https://road511.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;HOS break planning on the route&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Data Is Stale the Moment You Cache It
&lt;/h2&gt;

&lt;p&gt;Incidents, closures, and work zones are only useful while they're current. A closure feed you poll once an hour is a closure feed that's wrong for up to an hour — and a routing decision made on a stale closure is a driver sitting in a backup your system told them to enter. But polling every state's feed aggressively, parsing it, deduping it, and aging out the resolved events is its own piece of infrastructure that has nothing to do with your product.&lt;/p&gt;

&lt;p&gt;The honest answer here isn't "real-time magic" — it's that the freshness and lifecycle work should already be done by the time you query. Events carry their own start and end times and move from active to archived on their own; you ask for what's current on a corridor and get the current answer, with the polling cadence handled upstream. Pair it with the camera feeds for visual confirmation (&lt;a href="https://road511.com/blog/traffic-camera-feeds-api.html" rel="noopener noreferrer"&gt;traffic camera feeds&lt;/a&gt;) and the dispatcher has eyes on the corridor without standing up a single scraper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Thread
&lt;/h2&gt;

&lt;p&gt;Every one of these is the same shape of problem: the data exists, but it's fragmented across authorities, published in incompatible formats, and only useful once it's joined to a route and kept fresh. Each one is solvable in-house — and each one, solved in-house, becomes a permanent maintenance commitment that competes with the product you actually sell.&lt;/p&gt;

&lt;p&gt;That's the entire premise of Road511: do that aggregation once, for everyone, and hand you one normalized API — events, features, bridge clearances, restrictions, routing with corridor hazards — instead of fifty integrations to keep alive. You spend your engineering time on the part that makes your product yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://road511.com/docs.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; — the &lt;code&gt;events&lt;/code&gt;, &lt;code&gt;features&lt;/code&gt;, and truck-routing surface in one reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.road511.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — see the normalized data across states and provinces&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://road511.com/blog/north-american-road-data-challenges.html" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>trucking</category>
      <category>api</category>
      <category>logistics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hours-of-Service Break Planning, Right on the Route</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Mon, 22 Jun 2026 10:00:44 +0000</pubDate>
      <link>https://dev.to/road511/hours-of-service-break-planning-right-on-the-route-2end</link>
      <guid>https://dev.to/road511/hours-of-service-break-planning-right-on-the-route-2end</guid>
      <description>&lt;p&gt;A consumer nav app tells the driver where to turn. It will not tell the dispatcher where the 11-hour driving clock runs out — and, more importantly, whether there is legal parking when it does. That second question is the one that strands a truck at 11&amp;nbsp;PM on the shoulder of an off-ramp with every nearby lot already full.&lt;/p&gt;

&lt;p&gt;Road511’s routing endpoint now answers it. Send the driver’s Hours-of-Service clock along with the route, and the response carries an &lt;code&gt;hos[]&lt;/code&gt; array: every point on the corridor where the driver must take a break or stop driving under the selected regime, the projected time they reach it, the legal deadline, and — the part that matters operationally — the truck parking and rest areas actually reachable before that deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;It rides the same call as everything else routing does: &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt;. You already send an origin, a destination, and a &lt;code&gt;truck&lt;/code&gt; profile. To get the HOS projection, add an &lt;code&gt;hos&lt;/code&gt; block &lt;em&gt;inside&lt;/em&gt; the truck object describing the driver’s clock at departure.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST "https://api.road511.com/api/v1/routing/route" \
  -H "X-API-Key: your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "origin":      { "lat": 41.8781, "lng": -87.6298 },
    "destination": { "lat": 39.7392, "lng": -104.9903 },
    "departure_time": "2026-06-08T06:00:00Z",
    "truck": {
      "profile":  "tractor",
      "weight_t": 36.0,
      "height_m": 4.2,
      "axles":    5,
      "hos": {
        "ruleset":           "us",
        "drive_remaining_s": 39600,
        "duty_remaining_s":  50400,
        "since_break_s":     0
      }
    },
    "enrichment": {
      "include_features": ["truck_parking", "rest_areas"]
    }
  }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s a Chicago→Denver run for a fresh driver on US rules: 11 hours of driving left (&lt;code&gt;39600&lt;/code&gt; s), a 14-hour duty window (&lt;code&gt;50400&lt;/code&gt; s), and zero driving time since the last break. Every counter is “seconds remaining” against the named ruleset’s limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HOS Clock
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;hos&lt;/code&gt; object is the driver’s state, not a fixed policy. Store only the &lt;code&gt;ruleset&lt;/code&gt; on a reusable truck profile — the per-trip counters are supplied inline on each request and merge on top.&lt;/p&gt;

&lt;p&gt;Field&lt;/p&gt;

&lt;p&gt;Meaning&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ruleset&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The regime: &lt;code&gt;us&lt;/code&gt;, &lt;code&gt;canada_south&lt;/code&gt;, or &lt;code&gt;eu&lt;/code&gt;. Empty defaults from the deployment region.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drive_remaining_s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Driving-limit budget left.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;duty_remaining_s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On-duty window budget left.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;since_break_s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Driving time accrued since the last qualifying break.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;elapsed_remaining_s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Canada’s elapsed (wall-clock) window budget.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cycle_remaining_s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Weekly / cycle budget (60/70-hour US, 70/120-hour Canada, 56h/90h EU).&lt;/p&gt;

&lt;p&gt;Any counter you leave out is read as “a fresh driver” — the ruleset’s full limit. The counters are pointers under the hood, so an explicit &lt;code&gt;0&lt;/code&gt; (“out of hours right now”) is distinct from “not supplied.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Response
&lt;/h2&gt;

&lt;p&gt;The HOS projection is attached to the primary route as an &lt;code&gt;hos[]&lt;/code&gt; array, alongside the &lt;code&gt;warnings[]&lt;/code&gt; and &lt;code&gt;features[]&lt;/code&gt; you may already be using.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "routes": [
    {
      "summary": { "distance_m": 1480200, "duration_s": 54600 },
      "geometry": { "type": "LineString", "coordinates": [ /* ... */ ] },
      "hos": [
        {
          "reason": "break_required",
          "distance_along_route_m": 642000,
          "projected_time":  "2026-06-08T14:00:00Z",
          "legal_deadline":  "2026-06-08T14:00:00Z",
          "feasible": true,
          "suggested_stops": [
            { "type": "rest_area", "name": "I-80 Walcott Rest Area",
              "distance_along_route_m": 631500, "capacity": 22,
              "geometry": { "type": "Point", "coordinates": [-90.7830, 41.5900] } },
            { "type": "truck_parking", "name": "I-80 Tipton Truck Parking",
              "distance_along_route_m": 612000,
              "capacity": 140, "available_spots": 31,
              "as_of": "2026-06-08T13:20:00Z",
              "geometry": { "type": "Point", "coordinates": [-91.1300, 41.7700] } }
          ]
        },
        {
          "reason": "drive_limit",
          "distance_along_route_m": 905800,
          "projected_time":  "2026-06-08T17:30:00Z",
          "legal_deadline":  "2026-06-08T17:30:00Z",
          "feasible": false,
          "suggested_stops": []
        }
      ]
    }
  ],
  "route_id": "rt_5d91c7e2"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each stop carries a &lt;code&gt;reason&lt;/code&gt; — the limit that trips there — the &lt;code&gt;distance_along_route_m&lt;/code&gt; where it lands, the &lt;code&gt;projected_time&lt;/code&gt; the driver reaches it, and the &lt;code&gt;legal_deadline&lt;/code&gt; the rule allows. The four reasons:&lt;/p&gt;

&lt;p&gt;Reason&lt;/p&gt;

&lt;p&gt;What tripped&lt;/p&gt;

&lt;p&gt;&lt;code&gt;break_required&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The driver hit the drive-time-since-break limit and owes a rest break.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drive_limit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The daily driving limit is exhausted — the driver must stop for the day.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;duty_window&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The on-duty window closed (or, in Canada, the elapsed window) before the driving limit did.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cycle_limit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The weekly/cycle budget ran out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part That Matters: Reachable Parking
&lt;/h2&gt;

&lt;p&gt;Projecting &lt;em&gt;where&lt;/em&gt; the clock runs out is the easy half. The operationally useful half is whether the driver can actually stop there. Each &lt;code&gt;hos&lt;/code&gt; stop attaches a &lt;code&gt;suggested_stops[]&lt;/code&gt; list — the truck parking, truck rest areas, and rest areas reachable &lt;em&gt;before&lt;/em&gt; the legal deadline, ordered closest-to-the-deadline first so the driver squeezes the most legal driving out of the day. That list is drawn from Road511’s own live 511 parking data, the same normalized feeds behind the rest of the API.&lt;/p&gt;

&lt;p&gt;And the flag that no consumer navigation app surfaces: &lt;code&gt;feasible&lt;/code&gt;. When it’s &lt;code&gt;true&lt;/code&gt;, at least one legal stop is reachable in time. When it’s &lt;code&gt;false&lt;/code&gt; — as on the second stop above — &lt;strong&gt;no legal parking is reachable before the deadline&lt;/strong&gt;. That is a real compliance risk you want to see at dispatch time, not at the roadside. (&lt;code&gt;feasible&lt;/code&gt; is omitted entirely if the parking lookup didn’t run, so a missing flag never reads as a misleading “false.”)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Regimes
&lt;/h2&gt;

&lt;p&gt;Four Hours-of-Service regimes ship today, each implemented against its actual regulation:&lt;/p&gt;

&lt;p&gt;Ruleset&lt;/p&gt;

&lt;p&gt;Regulation&lt;/p&gt;

&lt;p&gt;Key limits&lt;/p&gt;

&lt;p&gt;&lt;code&gt;us&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;49 CFR 395 (FMCSA, property-carrying)&lt;/p&gt;

&lt;p&gt;11h driving, 14h duty window, 30-min break after 8h driving, 10h daily rest&lt;/p&gt;

&lt;p&gt;&lt;code&gt;canada_south&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;SOR/2005-313 (south of 60°N)&lt;/p&gt;

&lt;p&gt;13h driving, bounded by both a 14h duty window and a 16h elapsed window, 10h daily rest&lt;/p&gt;

&lt;p&gt;&lt;code&gt;canada_north&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;SOR/2005-313 (north of 60°N)&lt;/p&gt;

&lt;p&gt;15h driving, 18h duty window, 8h daily rest&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eu&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Regulation (EC) No 561/2006&lt;/p&gt;

&lt;p&gt;9h driving, 45-min break after 4.5h driving, 11h daily rest&lt;/p&gt;

&lt;p&gt;The weekly/cycle caps (US 60/70-hour, Canada 70/120-hour, EU 56h/90h) are modeled when you supply &lt;code&gt;cycle_remaining_s&lt;/code&gt;; left unset, the projection focuses on the daily limits. The European &lt;code&gt;aetr&lt;/code&gt; regime is planned. If you don’t set a ruleset, the routing handler defaults it from the deployment region — but Canadian operators should set &lt;code&gt;canada_south&lt;/code&gt; or &lt;code&gt;canada_north&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Fits
&lt;/h2&gt;

&lt;p&gt;The HOS projection is computed on the route geometry that’s already in hand — it’s pure projection over the polyline, not a second round of routing. It runs for the primary route only, and it rides the routing call you’re already making: &lt;strong&gt;no separate endpoint, no separate charge&lt;/strong&gt; beyond the routing request itself. Routing is a paid-plan feature (featured on Pro+) with a free 14-day trial, so you can wire HOS into a dispatch flow and try it end-to-end before committing.&lt;/p&gt;

&lt;p&gt;Pair it with the corridor &lt;code&gt;warnings[]&lt;/code&gt; (bridge clearances, weight restrictions, rail crossings, work zones) and the same request answers three questions at once: can this truck legally run this corridor, where will the driver have to stop, and is there parking when they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="///docs.html#truck-routing"&gt;API docs&lt;/a&gt; — full request and response reference for &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="///blog/truck-routing-hazard-warnings.html"&gt;Truck routing with live hazard warnings&lt;/a&gt; — the corridor enrichment this builds on&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — no credit card, 14-day trial&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Plan the route, the legal stop, and the parking — in one call
&lt;/h3&gt;

&lt;p&gt;Send the driver’s HOS clock with the route and get back every required break, every drive-limit stop, and the parking reachable before each deadline. Free 14-day trial. No credit card.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Get Free API Key&lt;/a&gt; &lt;a href="///docs.html#truck-routing"&gt;Read the Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trucking</category>
      <category>api</category>
      <category>go</category>
      <category>logistics</category>
    </item>
    <item>
      <title>Normalizing DATEX II Across 30 European National Access Points</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Fri, 05 Jun 2026 11:37:15 +0000</pubDate>
      <link>https://dev.to/road511/normalizing-datex-ii-across-30-european-national-access-points-2g58</link>
      <guid>https://dev.to/road511/normalizing-datex-ii-across-30-european-national-access-points-2g58</guid>
      <description>&lt;p&gt;EU Directive 2010/40/EU mandates that every Member State operates a &lt;strong&gt;National Access Point (NAP)&lt;/strong&gt; that publishes real-time traffic, safety, and multimodal travel data in &lt;strong&gt;DATEX II&lt;/strong&gt;. On paper, that means one standard, 27 publishers, one integration.&lt;/p&gt;

&lt;p&gt;In practice, every NAP serves a different DATEX II profile, over a different transport, behind a different authentication flow, on a different cadence. "DATEX II compliant" is a checkbox each Member State ticks differently.&lt;/p&gt;

&lt;p&gt;This is what we ran into building &lt;a href="https://napspan.com" rel="noopener noreferrer"&gt;NAPSPAN&lt;/a&gt;, a unified API across EU 27 + UK + EFTA. Here's the architecture that lets us treat 30 incompatible DATEX II feeds as if they were one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spec, Then Reality
&lt;/h2&gt;

&lt;p&gt;DATEX II is a mature European standard maintained by &lt;a href="https://datex2.eu/" rel="noopener noreferrer"&gt;datex2.eu&lt;/a&gt; and coordinated by &lt;a href="https://napcore.eu/" rel="noopener noreferrer"&gt;NAPCORE&lt;/a&gt;. Each delegated regulation references it as the mandatory exchange format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;(EU) 2022/670&lt;/strong&gt; — RTTI: real-time events, speeds, road conditions&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;(EU) No 886/2013&lt;/strong&gt; — SRTI: free, open access to safety alerts&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;(EU) No 885/2013&lt;/strong&gt; — SSTP: safe and secure truck parking&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;(EU) 2017/1926&lt;/strong&gt; — MMTIS: multimodal travel information&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AFIR Article 20&lt;/strong&gt; — live EV charging-station data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the spec is clear. The implementation is anything but.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the NAPs Actually Diverge
&lt;/h2&gt;

&lt;p&gt;"DATEX II compliant" hides at least five axes of divergence:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Profile
&lt;/h3&gt;

&lt;p&gt;Each Member State publishes a national &lt;em&gt;profile&lt;/em&gt; — a constrained subset of the DATEX II model. Germany's profile (&lt;em&gt;German Traffic Data Profile&lt;/em&gt;) defines incidents, road works, and traffic flow on Autobahn + Bundesstraßen. France's &lt;a href="https://transport.data.gouv.fr/" rel="noopener noreferrer"&gt;transport.data.gouv.fr&lt;/a&gt; uses different sub-elements. The Netherlands' NDW publishes a tighter event taxonomy. The UK's &lt;a href="https://nap.tdt.gov.uk/" rel="noopener noreferrer"&gt;NAP TDT&lt;/a&gt; wraps DATEX into TIH-specific containers. The &lt;a href="https://repo.datex2.eu/implementations/profile_directory/" rel="noopener noreferrer"&gt;DATEX II profiles directory&lt;/a&gt; lists them all — same root schema, every country a different shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Version
&lt;/h3&gt;

&lt;p&gt;DATEX II v2.3 and v3.x are both in the wild. Some NAPs publish both. Some are mid-migration. The XML namespaces are different. Some elements were renamed between versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Transport
&lt;/h3&gt;

&lt;p&gt;DATEX II is a payload format, not a transport. NAPs ship it over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;HTTPS pull&lt;/strong&gt; — you GET an XML document on a schedule&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;HTTPS push (subscription)&lt;/strong&gt; — you register an endpoint, the NAP POSTs to you&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SOAP-over-HTTPS&lt;/strong&gt; — envelope-wrapped pull&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;OCIT-C&lt;/strong&gt; — the German telematics interface used by Mobilithek&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;WFS / GeoJSON&lt;/strong&gt; — some city-level supplements (Hamburg, Düsseldorf)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Authentication
&lt;/h3&gt;

&lt;p&gt;The matrix here is wide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;None&lt;/strong&gt; — SRTI safety feeds are mandated free and open&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Registered consumer (no key)&lt;/strong&gt; — NAP whitelists your origin or service endpoint&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Bearer / API key&lt;/strong&gt; — standard token in a header&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TLS client certificate (mTLS)&lt;/strong&gt; — Germany's Mobilithek&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;OAuth2&lt;/strong&gt; — some operator portals&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Cadence and freshness
&lt;/h3&gt;

&lt;p&gt;Some publications update every 30 seconds (motorway flow). Others refresh nightly (planned road-work calendars). Some sit behind a CDN that lies about freshness via stale &lt;code&gt;Last-Modified&lt;/code&gt; headers. Adaptive backoff matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Format Zoo, in DATEX II Form
&lt;/h2&gt;

&lt;p&gt;Here's roughly the same incident from three different NAPs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobilithek (DE)&lt;/strong&gt; — OCIT-C envelope wrapping a v2.3 &lt;code&gt;SituationPublication&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;d2:payloadPublication
    xsi:type="d2:SituationPublication"
    xmlns:d2="http://datex2.eu/schema/2/2_0"&amp;gt;
  &amp;lt;d2:situation id="DE_BAB7_4711"&amp;gt;
    &amp;lt;d2:situationRecord
        xsi:type="d2:Accident"
        id="DE_BAB7_4711_R1"&amp;gt;
      &amp;lt;d2:probabilityOfOccurrence&amp;gt;certain&amp;lt;/d2:probabilityOfOccurrence&amp;gt;
      &amp;lt;d2:groupOfLocations xsi:type="d2:Point"&amp;gt;
        &amp;lt;d2:pointByCoordinates&amp;gt;
          &amp;lt;d2:pointCoordinates&amp;gt;
            &amp;lt;d2:latitude&amp;gt;53.5511&amp;lt;/d2:latitude&amp;gt;
            &amp;lt;d2:longitude&amp;gt;9.9937&amp;lt;/d2:longitude&amp;gt;
          &amp;lt;/d2:pointCoordinates&amp;gt;
        &amp;lt;/d2:pointByCoordinates&amp;gt;
      &amp;lt;/d2:groupOfLocations&amp;gt;
    &amp;lt;/d2:situationRecord&amp;gt;
  &amp;lt;/d2:situation&amp;gt;
&amp;lt;/d2:payloadPublication&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;NDW (NL)&lt;/strong&gt; — v3.x with a tighter &lt;code&gt;VmsTablePublication&lt;/code&gt;-style envelope, JSON-LD permitted:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "@context": "https://datex2.eu/schema/3/jsonld",
  "publicationCreator": { "country": "nl", "nationalIdentifier": "NDW" },
  "situation": [{
    "id": "NDW_2026_05_01_42",
    "situationRecord": [{
      "@type": "Accident",
      "severity": "high",
      "groupOfLocations": {
        "locationContainedInGroup": [{
          "pointByCoordinates": { "latitude": 52.0907, "longitude": 5.1214 }
        }]
      }
    }]
  }]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;UK NAP TDT&lt;/strong&gt; — DATEX wrapped in a TIH container with a per-publisher feed envelope:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;tih:feed xmlns:tih="https://nap.tdt.gov.uk/tih"&amp;gt;
  &amp;lt;tih:publisher&amp;gt;National Highways&amp;lt;/tih:publisher&amp;gt;
  &amp;lt;d2:payloadPublication xsi:type="d2:SituationPublication"&amp;gt;
    &amp;lt;d2:situation id="NH-INC-9001"&amp;gt;...&amp;lt;/d2:situation&amp;gt;
  &amp;lt;/d2:payloadPublication&amp;gt;
&amp;lt;/tih:feed&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Same regulation. Same standard. Three different parsers required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The solution is the same one we use on the North American side: an adapter registry with one function signature.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type FetchFunc func(ctx context.Context, sr SourceResource) (*FetchResult, error)

type FetchResult struct {
    Events        []TrafficEvent
    Features      []Feature
    ResponseBytes int
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Every NAP adapter — whether it's pulling Mobilithek's OCIT-C broker, polling NDW's v3 JSON-LD, or unwrapping a TIH envelope — implements this one interface. It takes a source-resource config (URL, credentials, country code, profile version) and returns normalized events and features.&lt;/p&gt;

&lt;p&gt;Registration happens at init time:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func init() {
    Register("mobilithek", "events",         fetchMobilithekSituations)
    Register("mobilithek", "afir_charging",  fetchMobilithekAFIR)
    Register("mobilithek", "parking",        fetchMobilithekParking)
    Register("ndw",        "events",         fetchNDWSituations)
    Register("ndw",        "vms",            fetchNDWVMS)
    Register("uk_tdt",     "events",         fetchUKTDTSituations)
    Register("transport_data_gouv_fr", "events", fetchFRSituations)
    // ... 30 NAPs, ~120 (adapter, resource) registrations
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The scheduler doesn't know or care which DATEX II profile, version, or transport each adapter handles. It just calls &lt;code&gt;Fetch()&lt;/code&gt; and gets back normalized events and features.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Normalized Model
&lt;/h2&gt;

&lt;p&gt;Everything converges into two tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;traffic_events&lt;/code&gt;&lt;/strong&gt; — time-bounded incidents with lifecycle tracking:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type TrafficEvent struct {
    ID                 string
    Source             string      // "mobilithek", "ndw", "uk_tdt"
    Jurisdiction       string      // "DE", "NL", "GB"
    Type               EventType   // incident, construction, closure, weather
    Severity           Severity    // minor, moderate, major, critical
    Status             EventStatus // active, archived
    Title              string
    Description        string
    AffectedRoads      []string
    Direction          string
    LanesAffected      string
    Latitude, Longitude float64
    StartTime          time.Time
    EndTime            *time.Time
    EstimatedEndTime   *time.Time
    RoadClass          string      // motorway, trunk, primary, secondary
    Metadata           json.RawMessage // DATEX-II-specific fields preserved
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;features&lt;/code&gt;&lt;/strong&gt; — a generic table for everything else. Cameras, VMS signs, weather stations, parking sites, EV charging stations, truck-route segments, bridge clearances — all use the same table with a &lt;code&gt;feature_type&lt;/code&gt; discriminator and type-specific fields in a JSONB &lt;code&gt;properties&lt;/code&gt; column. Adding a new resource type requires zero schema migrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Profile Field Mapping
&lt;/h3&gt;

&lt;p&gt;The German profile uses &lt;code&gt;locationDescriptor&lt;/code&gt; with &lt;code&gt;roadName&lt;/code&gt;. The Dutch v3 profile uses a top-level &lt;code&gt;roadInformation&lt;/code&gt; structure. The UK feeds bury the road name three levels deep inside &lt;code&gt;tih:routeContext&lt;/code&gt;. Each adapter has a small mapping function that knows where its profile keeps each canonical field. Where a profile has nothing to map to, the field is left empty — we don't synthesize data we don't have.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lifecycle Tracking
&lt;/h3&gt;

&lt;p&gt;A DATEX II &lt;code&gt;SituationRecord&lt;/code&gt; has a &lt;code&gt;situationRecordVersion&lt;/code&gt; and a &lt;code&gt;situationRecordVersionTime&lt;/code&gt;. Each new pull may bring a new version of the same record. Some NAPs increment the version on every minor metadata change; others only on substantive updates.&lt;/p&gt;

&lt;p&gt;The solution: diff the current state against the previous fetch. Track every change in an &lt;code&gt;event_history&lt;/code&gt; table — severity changes, description updates, lane changes, archival. This enables analytics like clearance time percentiles and corridor reliability across borders.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Coordinate Reference Systems
&lt;/h3&gt;

&lt;p&gt;Most NAPs publish WGS84 (EPSG:4326). Some German state feeds publish ETRS89 / UTM zones. A few city feeds use the local cadastral system. PostGIS handles transformation, but each adapter has to know what it's receiving.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rate Limiting at Scale
&lt;/h3&gt;

&lt;p&gt;30 NAPs, each with multiple resource types (events, VMS, parking, EV charging, weather), each polling on a 30 sec to 5 min cadence. The scheduler uses per-server semaphores with configurable concurrency limits and request gaps. Circuit breakers back off on repeated failures. Adaptive backoff increases poll intervals when a NAP is slow or returning errors — important when a single NAP can otherwise drag down a whole region's freshness.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Schema-Free Feature Types
&lt;/h3&gt;

&lt;p&gt;When we started on the North American side, we had separate tables for cameras, VMS, parking. Every new data type meant a migration. The pivot to a generic &lt;code&gt;features&lt;/code&gt; table with JSONB properties was the best architectural decision in the project. On the EU side, that decision pays off again the moment AFIR EV charging data comes online — zero schema changes, just a new &lt;code&gt;feature_type&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;After all that normalization work, the API is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get active incidents in Germany:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "https://api.napspan.com/api/v1/events?jurisdiction=DEU&amp;amp;type=incident&amp;amp;status=active" \
  -H "X-API-Key: your_key"

{
  "data": [
    {
      "id": "de_mobilithek_4711",
      "jurisdiction": "DEU",
      "type": "incident",
      "severity": "major",
      "title": "Verkehrsunfall A7 Richtung Hamburg",
      "affected_roads": ["A7"],
      "direction": "Hamburg",
      "lanes_affected": "2 of 3 lanes closed",
      "latitude": 53.5511,
      "longitude": 9.9937,
      "start_time": "2026-05-01T08:15:00Z",
      "estimated_end_time": "2026-05-01T12:00:00Z"
    }
  ],
  "total": 142,
  "limit": 100,
  "offset": 0,
  "has_more": true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Get traffic cameras in the Netherlands as GeoJSON:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "https://api.napspan.com/api/v1/features/geojson?type=cameras&amp;amp;jurisdiction=NLD" \
  -H "X-API-Key: your_key"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Drop the response directly into Leaflet, Mapbox, or any GeoJSON-compatible tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query a cross-border box (Berlin to Amsterdam):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "https://api.napspan.com/api/v1/events?status=active&amp;amp;\
bbox=4.90,52.37,13.40,52.52" \
  -H "X-API-Key: your_key"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;One call, two NAPs (Mobilithek + NDW), one consistent response shape — a bounding box spans the border with no per-country fan-out. (For events tied to an actual driven path rather than a box, &lt;code&gt;POST /routing/route&lt;/code&gt; returns them as ranked &lt;code&gt;warnings[]&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Data
&lt;/h2&gt;

&lt;p&gt;Once normalized, the dataset includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Real-time &lt;strong&gt;RTTI&lt;/strong&gt; events — incidents, road works, traffic flow&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SRTI&lt;/strong&gt; safety alerts (free, open under 886/2013)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;VMS&lt;/strong&gt; dynamic message-sign content with current displayed text&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SSTP&lt;/strong&gt; safe and secure truck parking with availability&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AFIR&lt;/strong&gt; Article 20 EV charging-point status&lt;/li&gt;
&lt;li&gt;  Traffic cameras with image URLs (where the NAP exposes them)&lt;/li&gt;
&lt;li&gt;  RWIS-style weather-station readings&lt;/li&gt;
&lt;li&gt;  TEN-T core network corridor metadata&lt;/li&gt;
&lt;li&gt;  Bridge clearances and weight restrictions per Member State&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Go&lt;/strong&gt; — chi router, pgx v5, slog, encoding/xml + custom DATEX schema parsers&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PostgreSQL + PostGIS&lt;/strong&gt; — spatial queries, GeoJSON generation, corridor intersection&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Redis&lt;/strong&gt; — response caching, feature detail caching, rate-limit counters, webhook idempotency (&lt;strong&gt;mandatory&lt;/strong&gt; — the API and worker refuse to start if Redis is unreachable)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vue 3 + Leaflet&lt;/strong&gt; — &lt;a href="https://map.napspan.com" rel="noopener noreferrer"&gt;live traffic map&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The API is live with a free tier (no credit card):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://map.napspan.com" rel="noopener noreferrer"&gt;Live map&lt;/a&gt; — explore the data visually&lt;/li&gt;
&lt;li&gt;  &lt;a href="///docs.html"&gt;API docs&lt;/a&gt; — full endpoint reference&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://portal.napspan.com" rel="noopener noreferrer"&gt;Developer portal&lt;/a&gt; — sign up and get an API key&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're building anything on European mobility data — logistics, fleet routing, navigation, urban-mobility dashboards — we'd love to hear which NAPs and which DATEX II profiles you most need normalized first. The hardest part isn't the parsing; it's knowing which Member State publishes which data on which transport behind which auth flow. After 30 NAPs, we've built a pretty good map of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ready to try NAPSPAN?
&lt;/h3&gt;

&lt;p&gt;Free 14-day trial. No credit card. EU 27 + UK + EFTA, normalized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.napspan.com/signup" rel="noopener noreferrer"&gt;Get Free API Key&lt;/a&gt; &lt;a href="https://map.napspan.com/" rel="noopener noreferrer"&gt;Explore the Map&lt;/a&gt;&lt;/p&gt;

</description>
      <category>datex2</category>
      <category>eu</category>
      <category>go</category>
      <category>api</category>
    </item>
  </channel>
</rss>
