<?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>"In Betrieb" Is Not "Free". Live Connector Status Across Five European Countries.</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:10:53 +0000</pubDate>
      <link>https://dev.to/road511/in-betrieb-is-not-free-live-connector-status-across-five-european-countries-24fc</link>
      <guid>https://dev.to/road511/in-betrieb-is-not-free-live-connector-status-across-five-european-countries-24fc</guid>
      <description>&lt;p&gt;Every European country now publishes where its public charge points are. That was the easy half, and the Alternative Fuels Infrastructure Regulation is the reason it happened: since April 2024 operators owe machine-readable data about publicly accessible recharging points, and the national access points duly carry it.&lt;/p&gt;

&lt;p&gt;So a planner asks “is there a charger at the depot turn-off?” and gets a confident yes. Then the driver arrives and both connectors are occupied, and the answer was worthless — not wrong, worthless. It answered a question about concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Field That Looks Like an Answer and Isn’t
&lt;/h2&gt;

&lt;p&gt;Germany is the cleanest illustration, because its registry is genuinely good. We hold &lt;strong&gt;154,210&lt;/strong&gt; active German charging records, and &lt;strong&gt;141,083&lt;/strong&gt; of them carry a &lt;code&gt;status&lt;/code&gt; field. On &lt;strong&gt;116,422&lt;/strong&gt; it reads &lt;em&gt;In Betrieb&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That word means the station is registered and commissioned. It is a fact about paperwork and hardware, and it does not change when a van pulls up. &lt;strong&gt;Zero&lt;/strong&gt; of those 116,422 &lt;em&gt;In Betrieb&lt;/em&gt; records carry any availability field at all. A fleet system that reads &lt;code&gt;status&lt;/code&gt; and colours the pin green has not made a small error — it has answered a different question and presented it as this one.&lt;/p&gt;

&lt;p&gt;Germany did start publishing live state in mid-September — &lt;strong&gt;0&lt;/strong&gt; stations on 10 September, &lt;strong&gt;327&lt;/strong&gt; on 3 October, &lt;strong&gt;3,424&lt;/strong&gt; on 15 October. But it does not arrive on the registry record. It arrives over the AFIR dynamic channel as a &lt;em&gt;separate&lt;/em&gt; record, and &lt;strong&gt;2,863&lt;/strong&gt; of those 3,424 sit within 30 metres of a registry record that still carries no state. The same charger can therefore appear twice: once saying &lt;em&gt;In Betrieb&lt;/em&gt;, once saying whether the cable is free. We do not merge the two yet, so filter on &lt;code&gt;availability_status&lt;/code&gt; rather than on the registry row.&lt;/p&gt;

&lt;p&gt;The distinction matters because the two fields look interchangeable, and only one of them answers the driver’s question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Live Status Actually Exists, and How Much of It
&lt;/h2&gt;

&lt;p&gt;Measured in one transaction against production on &lt;strong&gt;15 October 2026, 15:04 UTC&lt;/strong&gt;: of &lt;strong&gt;415,272&lt;/strong&gt; active charging records we serve across Europe, &lt;strong&gt;121,988&lt;/strong&gt; carry a live availability status, and &lt;strong&gt;121,956&lt;/strong&gt; of those carry it &lt;em&gt;per connector&lt;/em&gt; rather than per site. That is &lt;strong&gt;323,765 individual connectors&lt;/strong&gt; whose state is published.&lt;/p&gt;

&lt;p&gt;It is five countries, and they arrive over four national channels — Germany and Lithuania share the AFIR dynamic one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jurisdiction&lt;/th&gt;
&lt;th&gt;Stations with live status&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Netherlands&lt;/td&gt;
&lt;td&gt;79,051&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nl_ocpi&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;22,079&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fr_irve_dynamique&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switzerland&lt;/td&gt;
&lt;td&gt;13,755&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ch_oicp&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lithuania&lt;/td&gt;
&lt;td&gt;3,679&lt;/td&gt;
&lt;td&gt;&lt;code&gt;afir_dynamic&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;td&gt;3,424&lt;/td&gt;
&lt;td&gt;&lt;code&gt;afir_dynamic&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that table twice before building on it. &lt;strong&gt;Five countries out of the thirty-six we carry&lt;/strong&gt; — 29.4% of our charging rows, not “Europe”, and the German line is 2.2% of the German records. Anyone selling you continental live charging coverage today is rounding up.&lt;/p&gt;

&lt;p&gt;At that same moment the network read: &lt;strong&gt;99,659 available&lt;/strong&gt;, &lt;strong&gt;10,251 full&lt;/strong&gt;, &lt;strong&gt;9,470 unknown&lt;/strong&gt;, &lt;strong&gt;2,608 out of service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those last two categories are the ones that get flattened by careless code. &lt;em&gt;Unknown&lt;/em&gt; is not &lt;em&gt;full&lt;/em&gt;, and &lt;em&gt;out of service&lt;/em&gt; is neither — a charger that is broken will still be broken in twenty minutes, which is a different routing decision from one that is merely busy now. We keep the four states separate for exactly that reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per Connector, Because a Station Is Not a Unit
&lt;/h2&gt;

&lt;p&gt;A site with two connectors is not half-free or fully-free; it is one cable in use and one cable open, and those two facts have different owners, different power ratings and different timestamps. The array comes back as it is published — here one Dutch station as it read on 10 September:&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=ev_charging&amp;amp;jurisdiction=NLD"&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;"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;"NLD-ev-ALL-NLLOC016859"&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;"NL, Van der Meerstraat 20, 3863 AG Nijkerk"&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;52.21755&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;5.472011&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;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allego"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ports"&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;"connector_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;"IEC_62196_T2"&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_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;"available"&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_points"&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;"occupied_points"&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;"unavailable_points"&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;"reported_points"&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;"availability_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;"nl_ocpi"&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_updated_at"&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-10T15:28:24Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refill_points"&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;"NLALLEGO0134202"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"occupied"&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;"CHARGING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&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-10T15:28:24Z"&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;"NLALLEGO0134201"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&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;"AVAILABLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&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-09T14:26:19Z"&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;Look at the two &lt;code&gt;updated_at&lt;/code&gt; stamps in that array. They are the same station, and they are &lt;strong&gt;a day apart&lt;/strong&gt;. The connector someone is actively using reported twenty seconds ago; its neighbour last said anything the previous afternoon. That is not a defect in the feed and it is not one in our ingestion — a connector reports when its state changes, so a quiet cable is a stale cable. It is, however, exactly the sort of thing a dashboard will average into a single meaningless “last updated” if nobody looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Things to Know Before You Build on It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Freshness is uneven, and one country cannot be aged at all.&lt;/strong&gt; Of the 121,988 live rows, &lt;strong&gt;107,160&lt;/strong&gt; carry an &lt;code&gt;availability_updated_at&lt;/code&gt;. Only &lt;strong&gt;18,112&lt;/strong&gt; of those — 17% — are inside the last hour; 90,968 are inside 24 hours; and the oldest stamp in the set is from &lt;strong&gt;14 October 2023&lt;/strong&gt;, a three-year-old reading that is still, technically, a live status field. Set your own staleness threshold and enforce it. Switzerland’s &lt;strong&gt;13,755&lt;/strong&gt; stations publish no timestamp whatsoever, so they cannot be aged by us or by you; treat them accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The per-connector status strings are not normalised, on purpose.&lt;/strong&gt; Across 323,765 connectors the same state arrives in three casings — &lt;code&gt;AVAILABLE&lt;/code&gt; 141,719, &lt;code&gt;available&lt;/code&gt; 49,731, &lt;code&gt;Available&lt;/code&gt; 15,156 — &lt;code&gt;unknown&lt;/code&gt; arrives in three as well, &lt;code&gt;occupied&lt;/code&gt; in two. We normalise the station-level &lt;code&gt;availability_status&lt;/code&gt; into four values and pass the array through as the operator wrote it, because collapsing a vendor’s string is how you silently lose a state nobody anticipated. Case-fold before you compare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a live reading, not a reservation.&lt;/strong&gt; An available connector is available to whoever reaches it first. Nothing in AFIR, OCPI or any national feed holds a bay for your driver, and a charger that was free when the route was planned is a probability by the time the vehicle arrives, not a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Call as Everything Else on the Corridor
&lt;/h2&gt;

&lt;p&gt;Charging state is not a separate product. It arrives in the same &lt;code&gt;/features&lt;/code&gt; schema, under the same jurisdiction filters, as the &lt;a href="https://napspan.com/blog/truck-parking-occupancy-api.html" rel="noopener noreferrer"&gt;live truck-parking occupancy&lt;/a&gt; that answers the parallel question for diesel fleets, the &lt;a href="https://napspan.com/blog/cross-border-truck-restrictions-vms.html" rel="noopener noreferrer"&gt;cross-border truck bans and VMS signs&lt;/a&gt;, and the &lt;a href="https://napspan.com/blog/toll-cost-routing-api.html" rel="noopener noreferrer"&gt;vehicle-class toll costs&lt;/a&gt; for the same corridor. One key, one schema, one set of jurisdiction codes — ISO 3166-1 alpha-3, so &lt;code&gt;NLD&lt;/code&gt; and &lt;code&gt;FRA&lt;/code&gt;, never &lt;code&gt;NL&lt;/code&gt; or &lt;code&gt;FR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The point of normalising four national channels into one field set is not tidiness. It is that &lt;code&gt;nl_ocpi&lt;/code&gt;, &lt;code&gt;fr_irve_dynamique&lt;/code&gt;, &lt;code&gt;ch_oicp&lt;/code&gt; and &lt;code&gt;afir_dynamic&lt;/code&gt; are four different documents with four different vocabularies, and a fleet that wants “is the connector free” should not have to learn all four to find out — nor to discover, the hard way, that another country answers a different question in a field with a similar name.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not European coverage.&lt;/strong&gt; Five countries carry live status; thirty-one do not. Germany, the largest registry we hold, carries it on 3,424 separate records — none of its 116,422 &lt;em&gt;In Betrieb&lt;/em&gt; registry records has it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not reserve anything.&lt;/strong&gt; See above; this is worth repeating because it is the assumption that costs a driver an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not tell you the price.&lt;/strong&gt; Tariff data is a separate and much messier problem; the fuel side of it is covered in &lt;a href="https://napspan.com/blog/per-station-fuel-prices-europe.html" rel="noopener noreferrer"&gt;per-station fuel prices&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An absent availability field means unknown, never free.&lt;/strong&gt; The 293,284 records without one are not empty car parks; they are stations whose operator publishes nothing about state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Figures measured against production on 15 October 2026, 15:04 UTC, in a single transaction so they stay mutually consistent; the JSON example is one real record as it read on 10 September, when this article first ran. Charging availability moves by the minute — the counts above describe that moment, and the shape of the finding rather than the numbers is what will still be true next week.&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=ev_charging&lt;/code&gt;, filtered by &lt;code&gt;jurisdiction&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 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/ev-charging-live-availability-europe.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=ev-charging-live-availability-europe&amp;amp;utm_content=dev-12" rel="noopener noreferrer"&gt;napspan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>dataengineering</category>
      <category>sustainability</category>
      <category>logistics</category>
    </item>
    <item>
      <title>The Truck's Actual Toll Bill, Returned With the Route</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 08 Oct 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/road511/the-trucks-actual-toll-bill-returned-with-the-route-330</link>
      <guid>https://dev.to/road511/the-trucks-actual-toll-bill-returned-with-the-route-330</guid>
      <description>&lt;p&gt;The fastest route and the cheapest route are rarely the same line on the map. The difference is usually tolls — and a passenger car’s toll rate has almost nothing to do with what a 5-axle, 36-tonne, placarded tractor-trailer pays at the same plaza. If your dispatch math uses a car estimate and a fudge factor, the invoice is going to surprise you.&lt;/p&gt;

&lt;p&gt;Road511’s routing endpoint returns the toll cost computed for the truck you’re actually moving. Opt in on a route request and you get back two things: a per-currency &lt;strong&gt;total&lt;/strong&gt; for the whole trip, and a &lt;strong&gt;per-section breakdown&lt;/strong&gt; naming each toll system and the fare that applies to your vehicle class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opt In With &lt;code&gt;include&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Toll pricing is opt-in, because computing it has an upstream cost — you only pay for it when you ask. Add &lt;code&gt;"tolls"&lt;/code&gt; to the &lt;code&gt;include&lt;/code&gt; array on &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt;:&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;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.road511.com/api/v1/routing/route"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "origin":      { "lat": 40.7128, "lng": -74.0060 },
    "destination": { "lat": 39.9526, "lng": -75.1652 },
    "currency": "USD",
    "truck": {
      "profile":  "tractor",
      "weight_t": 36.0,
      "height_m": 4.2,
      "axle_count": 5,
      "hazardous": ["flammable"]
    },
    "include": ["tolls"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s New York City→Philadelphia for a 5-axle placarded tractor. The &lt;code&gt;currency&lt;/code&gt; field (ISO 4217) controls what the fares are reported in; the &lt;code&gt;truck&lt;/code&gt; profile you already send is what makes the price the &lt;em&gt;truck’s&lt;/em&gt; price.&lt;/p&gt;

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

&lt;p&gt;Two places carry toll data. The route &lt;code&gt;summary&lt;/code&gt; gets a &lt;code&gt;toll_costs&lt;/code&gt; array — one entry per distinct currency, because a single trip can cross systems billed in different currencies (think a US–Canada run mixing USD and CAD). Each route &lt;code&gt;section&lt;/code&gt; gets a &lt;code&gt;tolls&lt;/code&gt; array naming the systems charged on that stretch and the fares within them.&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;"routes"&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;"summary"&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;"distance_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;153400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"duration_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"toll_costs"&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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.75&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="nl"&gt;"sections"&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;"distance_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;92100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"duration_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I-95 S via New Jersey Turnpike"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tolls"&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;"system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New Jersey Turnpike"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"fares"&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;"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;"Class 5 (5 axles)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;31.40&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;"payment_methods"&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="s2"&gt;"transponder"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"transponders"&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="s2"&gt;"E-ZPass"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"counted"&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="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;"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;"Class 5 (5 axles)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;39.25&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;"payment_methods"&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="s2"&gt;"videoToll"&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;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;"distance_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;61300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"duration_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Delaware River bridge 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;"tolls"&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;"system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Delaware River Port Authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"fares"&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;"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;"5-axle 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;"price"&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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.35&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;"payment_methods"&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="s2"&gt;"transponder"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"transponders"&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="s2"&gt;"E-ZPass"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"counted"&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="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;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;"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;"LineString"&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="w"&gt; &lt;/span&gt;&lt;span class="err"&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="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="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;"route_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;"rt_a17f93c0"&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;&lt;strong&gt;The fares under one toll system are alternatives, not line items — don’t add them up.&lt;/strong&gt; Each is the same charge under a different payment method (&lt;code&gt;payment_methods&lt;/code&gt;: &lt;code&gt;transponder&lt;/code&gt;, &lt;code&gt;videoToll&lt;/code&gt;, …) or time window (&lt;code&gt;applicable_times&lt;/code&gt;), and the driver pays exactly one of them. &lt;code&gt;summary.toll_costs&lt;/code&gt; has already picked one fare per system — the transponder tariff where one exists, otherwise the cheapest payment group — and marks it &lt;code&gt;counted: true&lt;/code&gt;. That total is the number you’d quote a customer or drop into a lane-cost model; the &lt;code&gt;sections[].tolls[]&lt;/code&gt; detail is where you see &lt;em&gt;which&lt;/em&gt; authority charges &lt;em&gt;what&lt;/em&gt;, and what the same stretch costs without a transponder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Priced for the Vehicle, Not a Sedan
&lt;/h2&gt;

&lt;p&gt;This is the whole point. The fare you get back is keyed to the truck profile on the request — axle count, gross weight, height, and the hazmat flag all factor into the classification the toll engine prices against. A 5-axle combination doesn’t get a 2-axle rate; a heavy load doesn’t get a light-vehicle rate. The &lt;code&gt;fares[].name&lt;/code&gt; echoes the class that was applied (e.g. &lt;em&gt;“Class 5 (5 axles)”&lt;/em&gt;) so you can confirm the truck was rated the way you expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing Around Tolls
&lt;/h2&gt;

&lt;p&gt;Sometimes the answer is to skip the toll road entirely. That’s a different field: put &lt;code&gt;"toll_road"&lt;/code&gt; in the &lt;code&gt;avoid&lt;/code&gt; array and the engine routes around toll facilities where it can. The avoidance applies to every route in the response, so on its own it only shows you the detour.&lt;/p&gt;

&lt;p&gt;To see the trade-off, ask for it instead: add &lt;code&gt;"toll_alternative"&lt;/code&gt; to &lt;code&gt;include&lt;/code&gt; (it implies &lt;code&gt;"tolls"&lt;/code&gt;). You get the fast tolled route as usual, plus a second, toll-free route on &lt;code&gt;toll_alternative&lt;/code&gt; with its extra distance and time set against the toll dollars it saves — the decision made explicit instead of a guess.&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;"origin"&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="err"&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="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;"destination"&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="err"&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="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;"truck"&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="err"&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="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;"include"&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="s2"&gt;"toll_alternative"&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;It computes two routes, so it is billed as two routing calls. Full shape in the &lt;a href="https://road511.com/docs.html#toll-alternative" rel="noopener noreferrer"&gt;API docs&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;A few honest notes so the numbers behave the way you expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Major corridors are well covered&lt;/strong&gt; — Interstate turnpikes, major bridges and tunnels, express lanes, and Canadian facilities like the 407 ETR price reliably for trucks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage thins on the margins.&lt;/strong&gt; Some secondary or rural toll facilities are known to the engine but may return a system name with no priced fare for a given vehicle class — treat a missing &lt;code&gt;price&lt;/code&gt; as “not priced,” not “free.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send &lt;code&gt;departure_time&lt;/code&gt; for an exact figure.&lt;/strong&gt; Some tolls change by time of day. Without a departure time the engine can’t resolve which window applies, and the total is the worst case for the chosen payment method rather than an optimistic one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It’s a planning estimate.&lt;/strong&gt; Toll pricing is computed at request time for the route and vehicle class; it’s built for dispatch and quoting, not for reconciling a transponder statement to the cent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No tolls, no array.&lt;/strong&gt; A toll-free route, or a request that didn’t opt in, simply omits &lt;code&gt;toll_costs&lt;/code&gt; and section &lt;code&gt;tolls&lt;/code&gt; — the response shape stays stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One Call, the Whole Trip
&lt;/h2&gt;

&lt;p&gt;Toll pricing rides the same &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt; as the corridor hazard &lt;code&gt;warnings[]&lt;/code&gt; and the &lt;a href="https://road511.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;Hours-of-Service break planning&lt;/a&gt;. A single request can hand back a drivable truck route, the clearances and restrictions on it, where the driver must legally stop, and what the trip costs in tolls — the operational picture a dispatcher actually needs before the truck rolls. Routing is on every paid plan from Starter up, and the free 14-day trial includes 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#truck-routing" rel="noopener noreferrer"&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="https://road511.com/blog/truck-routing-hazard-warnings.html" rel="noopener noreferrer"&gt;Truck routing with live hazard warnings&lt;/a&gt; — the corridor enrichment in the same call&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/toll-cost-routing-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=toll-cost-routing-api&amp;amp;utm_content=dev-02" 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>The Stop Your Route Never Mentions: 126,002 At-Grade Crossings</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Tue, 29 Sep 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/road511/the-stop-your-route-never-mentions-126002-at-grade-crossings-5f7l</link>
      <guid>https://dev.to/road511/the-stop-your-route-never-mentions-126002-at-grade-crossings-5f7l</guid>
      <description>&lt;p&gt;A placarded load does not get to treat a rail crossing as scenery. Under &lt;strong&gt;49 CFR 392.10&lt;/strong&gt;, a vehicle carrying placarded hazardous materials, a cargo tank — loaded or empty — or passengers must come to a full stop within 50 feet of the nearest rail, look and listen, and only then proceed. Not slow down. Stop.&lt;/p&gt;

&lt;p&gt;The rule has an exception that decides everything: it applies to &lt;em&gt;at-grade&lt;/em&gt; crossings. If the road goes over the track on a bridge, or under it, no stop is owed. So the operative question on a route is not “is there a crossing ahead” — it is &lt;strong&gt;which of the crossings ahead are at grade&lt;/strong&gt;. That is a data question with a data answer, and most routing products do not carry the field at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Inventory Actually Says
&lt;/h2&gt;

&lt;p&gt;Road511 normalizes the federal highway-rail grade crossing inventory into the same &lt;code&gt;features&lt;/code&gt; schema as everything else. Measured against our North American production database on &lt;strong&gt;20 August 2026&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reading&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;What it means for a placarded load&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public crossings carried&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;162,284&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Across 50 states; the inventory we serve is public crossings only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;… of which &lt;strong&gt;at grade&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;126,002&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A stop is owed at each one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;… grade separated (road over or under)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36,282&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No stop owed — and no reason to slow a schedule for one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrying a warning-device code&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;129,610&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What physically protects the crossing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrying no device code at all&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32,674&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The inventory does not say — treat as unknown, never as “none”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With a whistle ban in force&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5,047&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A quiet zone: the horn you would have heard is not coming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With an emergency contact number&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;155,510&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number to call when a vehicle is stopped on the tracks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly one crossing in five carries no device code, and among the 129,610 that do, &lt;strong&gt;58,355 — 45% — are passive-only&lt;/strong&gt;: crossbucks, a stop sign, or nothing that moves. Passive is not a defect in the data; it is the condition of the crossing, and it is exactly the condition where the driver’s stop is the last control in the system.&lt;/p&gt;

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

&lt;p&gt;Crossings ride the same single call to &lt;code&gt;POST /api/v1/routing/route&lt;/code&gt; that returns the truck-aware route and its corridor warnings. What the route returns depends on what you are hauling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;With &lt;code&gt;hazardous&lt;/code&gt; set on the truck profile&lt;/strong&gt;, every public at-grade crossing on the corridor comes back as a &lt;code&gt;critical&lt;/code&gt; warning citing 49 CFR 392.10. Every one — not a filtered selection, because the rule is not selective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Without it&lt;/strong&gt;, the noise would be intolerable, so only crossings that are operationally interesting surface: those the inventory reports at &lt;strong&gt;20 or more trains a day&lt;/strong&gt; (20,840 of them nationally), plus any a source reports as humped, where a low trailer can ground out on the crest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A dispatcher planning a placarded run gets a count of mandatory stops before the truck leaves — which is a schedule input, not a safety poster. Twenty stops on a lane is roughly twenty times a full deceleration and re-acceleration for a 36-tonne combination, and it is the difference between an ETA that holds and one that does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query It Directly
&lt;/h2&gt;

&lt;p&gt;The same records are on &lt;code&gt;/features&lt;/code&gt;, filterable by jurisdiction and bounding box:&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=rail_crossings&amp;amp;jurisdiction=IN&amp;amp;limit=5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each record carries the USDOT crossing number, the railroad, the road name, the grade position, the warning device, day and night through-train counts, track counts, the highway AADT the inventory holds, the maximum timetable speed for the track, and where published, the emergency contact number posted at the crossing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Data Is Not
&lt;/h2&gt;

&lt;p&gt;Four limits, stated plainly, because a compliance feature that oversells itself is worse than none:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not know whether a train is coming.&lt;/strong&gt; Train counts are inventory averages — how much traffic the crossing normally sees, not a live movement. Nothing in this feed is a real-time train position, and no product should imply one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is an inventory, not a sensor.&lt;/strong&gt; Records are maintained by railroads and state agencies and change on their schedule, not by the minute. Treat the device, track and traffic figures as the last thing the agencies published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private crossings are out.&lt;/strong&gt; We carry public crossings; the industrial and agricultural ones are excluded, so a facility approach road may hold a crossing this data does not describe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An absent field is unknown, never zero.&lt;/strong&gt; The 32,674 crossings without a device code are not 32,674 unprotected crossings. If your UI colours them, colour them grey.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stop itself is also not ours to grant or excuse: 392.10 carries its own exceptions — a streetcar crossing, an industrial switching track, a crossing directed by a flagman or a police officer, one protected by a functioning gate or signal showing clear. The data tells you the crossing is at grade and what protects it. The driver, and the carrier’s policy, decide the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance
&lt;/h2&gt;

&lt;p&gt;The crossing inventory is the federal highway-rail grade crossing inventory published by the US Department of Transportation, in the public domain. Coordinates and road names come with it. Everything above is a count you can reproduce against the live API.&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; — features, routing, and the GeoJSON variants 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; — crossings alongside the rest of the normalized 511 data.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://portal.road511.com/signup" rel="noopener noreferrer"&gt;Free API key&lt;/a&gt; — 14-day trial, no credit card.&lt;/li&gt;
&lt;/ul&gt;




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

</description>
      <category>api</category>
      <category>trucking</category>
      <category>logistics</category>
      <category>data</category>
    </item>
    <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>
  </channel>
</rss>
