<?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>Your Fuel Plan Uses One Diesel Price. Europe Publishes 42,323.</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/road511/your-fuel-plan-uses-one-diesel-price-europe-publishes-42323-43i4</link>
      <guid>https://dev.to/road511/your-fuel-plan-uses-one-diesel-price-europe-publishes-42323-43i4</guid>
      <description>&lt;p&gt;Diesel is the largest variable cost on almost every European haul, and it is the one a planner can actually move. Tolls are set by the route. Driver hours are set by &lt;a href="https://napspan.com/blog/hos-break-planning-api.html" rel="noopener noreferrer"&gt;561/2006&lt;/a&gt;. Fuel is set by where the truck stops — and that decision is usually made against a single national average published once a week.&lt;/p&gt;

&lt;p&gt;That average is not a price. It is a statistic computed over prices, and no station in Europe charges it. The gap between the statistic and the pump is where the money is, and it is bigger than most planning tools assume.&lt;/p&gt;

&lt;p&gt;Everything below was measured in a single query against NAPSPAN production at &lt;strong&gt;10:11 UTC on 29 October 2026&lt;/strong&gt;. Fuel prices move daily, so treat the figures as a dated observation and re-run the queries yourself — each one is a live API call, not a number we are asking you to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Countries Publish the Pump. Thirty-Five Publish the Building.
&lt;/h2&gt;

&lt;p&gt;NAPSPAN carries &lt;strong&gt;138,283 active fuel stations across 39 European countries&lt;/strong&gt;. Of those, &lt;strong&gt;42,323 carry an actual price&lt;/strong&gt;, and they come from exactly three national registers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Stations in NAPSPAN&lt;/th&gt;
&lt;th&gt;Carrying a price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Italy&lt;/td&gt;
&lt;td&gt;24,142&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21,652&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spain&lt;/td&gt;
&lt;td&gt;11,418&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11,371&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;9,821&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,300&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The other 36 countries&lt;/td&gt;
&lt;td&gt;~92,900&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is the honest shape of European per-station fuel data, and it is worth internalising before anyone scopes a project around it. Everywhere else — Germany, Poland, the Netherlands, the Nordics, the whole of the Balkans — a European fuel layer means &lt;em&gt;locations&lt;/em&gt;. Where the forecourt is, what brand is on the sign, sometimes the opening hours. Not what it costs. We say why further down, because the reason is not technical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Average Hides the Decision
&lt;/h2&gt;

&lt;p&gt;Take the base diesel grade in each of the three priced countries at the snapshot moment. Not the cheapest headline, not the outliers — the 10th percentile, the median and the 90th percentile of every priced station in the country:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Priced diesel pumps&lt;/th&gt;
&lt;th&gt;10th pct&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;90th pct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spain (&lt;code&gt;gasoleo_a&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;11,199&lt;/td&gt;
&lt;td&gt;€1.739&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;€1.935&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€2.019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italy (&lt;code&gt;gasolio_self&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;20,289&lt;/td&gt;
&lt;td&gt;€2.190&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;€2.249&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€2.349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;France (&lt;code&gt;gazole&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;8,993&lt;/td&gt;
&lt;td&gt;€2.250&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;€2.399&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;€2.478&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One note on the Spanish column before anyone plans a route with it: the Canary Islands, Ceuta and Melilla sit well below the peninsula and are not reachable by road from anywhere in this post. Their effect on the figure is small — drop the Canaries and the Spanish 10th percentile moves from €1.739 to €1.759 — but it is the kind of thing worth knowing you have in your denominator.&lt;/p&gt;

&lt;p&gt;Read the medians across the rows and the first lever appears: &lt;strong&gt;€0.464 per litre between the French and Spanish medians&lt;/strong&gt;. On a 600-litre fill that is &lt;strong&gt;€278&lt;/strong&gt;, decided entirely by which side of a line the driver stops on.&lt;/p&gt;

&lt;p&gt;National medians are still an abstraction, though, so narrow it to the regions on either side of the Pyrenean crossings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Priced diesel pumps&lt;/th&gt;
&lt;th&gt;Median diesel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Navarre (&lt;code&gt;ESP-NC&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;€1.861&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalonia (&lt;code&gt;ESP-CT&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1,443&lt;/td&gt;
&lt;td&gt;€1.899&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aragon (&lt;code&gt;ESP-AR&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;418&lt;/td&gt;
&lt;td&gt;€1.935&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basque Country (&lt;code&gt;ESP-PV&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;341&lt;/td&gt;
&lt;td&gt;€1.989&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nouvelle-Aquitaine (&lt;code&gt;FRA-NAQ&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1,077&lt;/td&gt;
&lt;td&gt;€2.389&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Occitanie (&lt;code&gt;FRA-OCC&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;952&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;€2.395&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Occitanie to Navarre is &lt;strong&gt;€0.534 per litre&lt;/strong&gt; — roughly &lt;strong&gt;€320 on a 600-litre fill&lt;/strong&gt;, over a distance a truck covers inside a single driving period. No weekly national figure on either side of the border contains that fact.&lt;/p&gt;

&lt;p&gt;And there is a second lever that sits entirely inside one country. French stations are tagged with the road class they sit on. At the snapshot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;429 French stations on the autoroute network&lt;/strong&gt; — median &lt;code&gt;gazole&lt;/code&gt; &lt;strong&gt;€2.481&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8,564 French stations off it&lt;/strong&gt; — median &lt;code&gt;gazole&lt;/code&gt; &lt;strong&gt;€2.399&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An &lt;strong&gt;€0.082 per litre&lt;/strong&gt; motorway premium, or about &lt;strong&gt;€49 a fill&lt;/strong&gt;. A truck routed down an autoroute passes the expensive 429 by default; the cheaper 8,564 are the ones you have to deliberately look for, which requires knowing where they are and what they charge before dispatch, not after the fuel card statement arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  One &lt;code&gt;/features&lt;/code&gt; Call, Prices Included
&lt;/h2&gt;

&lt;p&gt;Per-station prices arrive in the same &lt;code&gt;fuel_stations&lt;/code&gt; feature type as every other forecourt in the system, filtered the same way as any other layer:&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=fuel_stations&amp;amp;jurisdiction=FRA-IDF"&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;"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;"fuel_stations"&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;"FRA-IDF"&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;"180 Rue d'Herblay, Taverny"&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;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Taverny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Île-de-France"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"highway_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;"R"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prices"&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;"gazole"&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;"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;2.179&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"litre"&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;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gazole"&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"&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-27T07:32:05+00:00"&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;"sp95"&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;"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;2.059&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"litre"&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;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SP95"&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"&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-27T07:32:05+00:00"&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;"sp98"&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;"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;2.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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"litre"&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;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SP98"&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"&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-27T07:32:05+00:00"&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;"fuels_available"&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;"Gazole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SP95"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SP98"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fuels_unavailable"&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;"E85"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GPLc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"E10"&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;Note &lt;code&gt;fuels_unavailable&lt;/code&gt; in passing: the French register also states which grades a station is currently &lt;em&gt;out of&lt;/em&gt;, which is a different and occasionally more urgent fact than what it charges.&lt;/p&gt;

&lt;p&gt;Three things about that payload will save you a week each.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. There is no &lt;code&gt;diesel&lt;/code&gt; key
&lt;/h3&gt;

&lt;p&gt;Diesel is &lt;code&gt;gazole&lt;/code&gt; in France, &lt;code&gt;gasoleo_a&lt;/code&gt; in Spain, and &lt;code&gt;gasolio_self&lt;/code&gt; in Italy — and those are just the base grades. Across the three registers the snapshot counts &lt;strong&gt;130 distinct grade keys&lt;/strong&gt;: &lt;strong&gt;6 in France, 14 in Spain, and 110 in Italy&lt;/strong&gt;, where branded premium diesels are reported as separate products under their marketing names. Any integration that looks up a hard-coded &lt;code&gt;"diesel"&lt;/code&gt; field returns nothing in all three countries.&lt;/p&gt;

&lt;p&gt;We keep the national keys rather than flattening them, because flattening loses information you need — a premium diesel is not the same product as the base grade, and pricing a fleet fill against the wrong one is a real error. Map the base grades once, ignore the rest, and you are done.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. In Italy the same pump has two prices
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;12,875 Italian stations publish both a self-service and an attended diesel price&lt;/strong&gt;, and the median difference between them is &lt;strong&gt;€0.200 per litre&lt;/strong&gt; — with a maximum, at one site, of &lt;strong&gt;€0.806&lt;/strong&gt;. That is &lt;strong&gt;€120&lt;/strong&gt; on a 600-litre fill riding on which of two keys you happened to read.&lt;/p&gt;

&lt;p&gt;It is the most expensive misreading this dataset makes available, and it is invisible if you assume one station means one price. &lt;code&gt;gasolio_self&lt;/code&gt; and &lt;code&gt;gasolio_servito&lt;/code&gt; are different products at the same address.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Freshness is not uniform, and we do not paper over it
&lt;/h3&gt;

&lt;p&gt;Each price carries its own &lt;code&gt;updated&lt;/code&gt; stamp where the source publishes one, separate from when we polled. That matters because the three registers do not move at the same speed. At the snapshot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;France&lt;/strong&gt; — all 8,993 priced diesel pumps carry a per-price timestamp. The median observation was from &lt;strong&gt;27 October 15:55 UTC&lt;/strong&gt;, under two days before the snapshot; 4,971 of them had been updated within 48 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Italy&lt;/strong&gt; — all 20,289 carry a timestamp. The newest read &lt;strong&gt;29 October 10:35&lt;/strong&gt; and the median &lt;strong&gt;28 October 13:31&lt;/strong&gt;, both Italian local time (see the format note below), so the median Italian price was about a day old and 15,231 of the 20,289 were inside 48 hours. In September the same register ran two to three days behind, with under 1% inside 48 hours — so do not hard-code a lag in either direction; read the stamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spain&lt;/strong&gt; — publishes &lt;strong&gt;no per-price timestamp at all&lt;/strong&gt;. You know when we last polled and nothing about when the operator last changed the number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So read &lt;code&gt;updated&lt;/code&gt; and age the value out on your own threshold. Italian and French prices are typically a day or two old, and the cadence has shifted between snapshots; a Spanish one is undated and must be treated accordingly. We would rather hand you the stamp and let you decide than stamp everything with our poll time and imply a freshness we did not measure.&lt;/p&gt;

&lt;p&gt;One caveat on that field, since it is the kind of thing that silently breaks an alert: &lt;strong&gt;the French stamps track the source's own publication clock, not true UTC.&lt;/strong&gt; At this snapshot &lt;strong&gt;144 of the 8,993&lt;/strong&gt; French stamps sat in the &lt;em&gt;future&lt;/em&gt; relative to our wall clock, the furthest ahead by &lt;strong&gt;17 minutes&lt;/strong&gt;. Compare these timestamps to each other, and do not build a “stale if older than &lt;em&gt;n&lt;/em&gt; minutes” rule that assumes they are synchronised with yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying by place
&lt;/h3&gt;

&lt;p&gt;And one more that costs an afternoon if you meet it in production: &lt;strong&gt;the stamp is not the same format in both countries that publish one.&lt;/strong&gt; France emits ISO 8601 with an offset (&lt;code&gt;2026-08-27T07:32:05+00:00&lt;/code&gt;); Italy emits &lt;code&gt;25/08/2026 08:02:02&lt;/code&gt; — day-first, space-separated, no timezone at all. We hand both through as the register wrote them rather than silently reinterpreting a date we cannot verify the zone of, so a parser pointed at ISO will throw on all &lt;strong&gt;20,289&lt;/strong&gt; Italian rows. Parse per country, and read an Italian stamp as local time.&lt;/p&gt;

&lt;p&gt;France and Spain are keyed by &lt;strong&gt;region&lt;/strong&gt; — 14 French and 19 Spanish subdivision codes carry priced stations (&lt;code&gt;FRA-OCC&lt;/code&gt;, &lt;code&gt;ESP-CT&lt;/code&gt;, and so on). Italy is a single national code, &lt;code&gt;ITA&lt;/code&gt;. The bare country codes &lt;code&gt;FRA&lt;/code&gt; and &lt;code&gt;ESP&lt;/code&gt; are separate jurisdiction rows of their own, not parents, and they do &lt;em&gt;not&lt;/em&gt; roll up their regions. At the snapshot, &lt;code&gt;ESP&lt;/code&gt; held a single record of its own, unpriced, and &lt;code&gt;FRA&lt;/code&gt; held 5 records of which &lt;strong&gt;4&lt;/strong&gt; carried a price. That second number is the dangerous one: a query on &lt;code&gt;jurisdiction=FRA&lt;/code&gt; returns four priced stations out of the country’s 9,300, so it neither errors nor comes back empty — it comes back looking like it worked. Use the regional codes, or skip codes entirely and ask spatially:&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?jurisdiction=FRA-OCC&amp;amp;type=fuel_stations&amp;amp;lat=43.60&amp;amp;lng=1.44&amp;amp;radius_km=50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where It Belongs: On the Route
&lt;/h2&gt;

&lt;p&gt;Querying a region is the analysis path. The one that changes a fuel bill is prices arriving already attached to the route the truck is driving.&lt;/p&gt;

&lt;p&gt;Fuel stations are a dense layer — mapped at essentially every interchange — so they ride a dedicated, lazy endpoint rather than the synchronous routing call. Compute a truck route, then ask for what is on it:&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/routing/route/saved/&lt;/span&gt;&lt;span class="nv"&gt;$ROUTE_ID&lt;/span&gt;&lt;span class="s2"&gt;/features?types=fuel_stations&amp;amp;buffer_m=300"&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;"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;"01HKZ9P5XJVQ6NB8E2RTH4FMW7"&lt;/span&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;"fuel_station"&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;"180 Rue d'Herblay, Taverny"&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;56200&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;120&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;"left"&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;"FRA-IDF"&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;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Taverny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"highway_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;"R"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prices"&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;"gazole"&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;"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;2.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;"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;"EUR"&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"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every match carries &lt;code&gt;distance_along_route_m&lt;/code&gt; (how far along the route it sits), &lt;code&gt;detour_m&lt;/code&gt; (how far off the line it is) and &lt;code&gt;side&lt;/code&gt; (left or right of the direction of travel) — the three fields that turn a list of nearby stations into a refuelling plan, because a cheap pump on the wrong carriageway is not cheap. The station's full &lt;code&gt;properties&lt;/code&gt;, prices included, pass through untouched. The call does not re-run the routing engine and does not count against routing quota.&lt;/p&gt;

&lt;p&gt;It rides the same corridor as the &lt;a href="https://napspan.com/blog/toll-cost-routing-api.html" rel="noopener noreferrer"&gt;toll cost&lt;/a&gt;, the &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;, the &lt;a href="https://napspan.com/blog/truck-parking-occupancy-api.html" rel="noopener noreferrer"&gt;live parking occupancy&lt;/a&gt; and the &lt;a href="https://napspan.com/blog/eu-border-queue-wait-times.html" rel="noopener noreferrer"&gt;border queues&lt;/a&gt;. One route, and the answer to &lt;em&gt;what will this trip cost and where does it stop&lt;/em&gt; comes back with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Cannot Get, and Why It Is Not a Coverage Gap
&lt;/h2&gt;

&lt;p&gt;The obvious question about a three-country fuel layer is why it is only three countries. The answer is worth publishing, because the constraint is legal rather than technical and every team that scopes this project discovers it the expensive way.&lt;/p&gt;

&lt;p&gt;Per-station prices exist in most of Europe. Reusing them commercially mostly does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Germany&lt;/strong&gt; — prices are collected by the official market-transparency unit for fuel. Redistributing them requires authorisation as a consumer information service under German competition law. That is an application, not an API key, and until it exists Germany stays a roadmap item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Austria&lt;/strong&gt; — the public price service is shaped by regulation to return only the &lt;em&gt;five cheapest&lt;/em&gt; stations per query, with no national bulk endpoint. A complete per-station layer is not merely restricted; it is impossible by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portugal&lt;/strong&gt; — per-station prices are published on a portal whose terms prohibit commercial use. The openly licensed distribution of the same dataset carries locations and metadata, without the prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Croatia&lt;/strong&gt; — prices are shown on a ministry website; a formal request for a machine-readable open-data release was refused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lithuania&lt;/strong&gt; — the national energy agency publishes strong daily per-station data and marks it all rights reserved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switzerland and Iceland&lt;/strong&gt; — no government per-station feed exists at all. What circulates is a motoring-club product in one case and a community scrape of oil-company sites in the other. Neither is licensed for redistribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A handful are genuinely open and simply not built yet — Cyprus publishes under a share-alike licence, Slovenia publishes without stating one, and Denmark's per-brand publication became a legal obligation on 1 January 2026. Those are engineering work. The list above is not.&lt;/p&gt;

&lt;p&gt;We would rather show you that map than colour in a continent and let you find out at integration time. A vendor claiming European per-station fuel coverage is either doing something we have concluded we cannot lawfully do, or is showing you locations and calling them prices.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three countries. That is the coverage.&lt;/strong&gt; France, Spain, Italy. The other 36 countries in the fuel layer are locations only. If your corridor is Rotterdam–Warsaw, this feature does nothing for you today, and we would rather say so now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every station in those three is priced.&lt;/strong&gt; Italy is 21,652 of 24,142 and France 9,300 of 9,821; Spain 11,371 of 11,418. In these three countries the station list now comes from the national registers alone, so an unpriced record is a register station with no current price, not a gap in the map. The absence of a &lt;code&gt;prices&lt;/code&gt; object means &lt;em&gt;unpriced&lt;/em&gt;, never &lt;em&gt;free&lt;/em&gt;. Your code needs that third state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Italian prices are about a day old.&lt;/strong&gt; The median stamp was roughly 22 hours behind at the snapshot, after running two to three days behind in September. Good enough to choose between two regions, not good enough to promise a driver a number at the pump.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spanish prices carry no observation time.&lt;/strong&gt; You have our poll time and nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;These are retail posted prices.&lt;/strong&gt; They are not your fuel-card contract price, and no register publishes negotiated fleet rates. The spread between regions and road classes is a real signal about where to buy; the absolute figure on your invoice will be your own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A price is a snapshot, not a forecast.&lt;/strong&gt; The medians above are what 29 October looked like. Re-pull before you rely on them — and if a number in this post matters to your decision, the API call that produced it is printed above precisely so you can check whether it still holds.&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;, and as the &lt;a href="https://napspan.com/blog/open-data-beyond-datex-ii.html" rel="noopener noreferrer"&gt;useful road data that lives outside the National Access Points&lt;/a&gt;. Three governments publish something genuinely valuable, properly, under open licences. They publish it in three different schemas, with three different words for diesel, on three different cadences, with three different attribution obligations — and one of them counts the same pump twice on purpose. Doing that join once per country, and then keeping it working, is a permanent maintenance commitment that competes with whatever you actually sell.&lt;/p&gt;

&lt;p&gt;NAPSPAN does the join once. The prices are open data and the credit travels with every record we hand you: French data under Licence Ouverte, with the &lt;em&gt;Ministères économiques et financiers&lt;/em&gt; as licensor; Spanish data as &lt;em&gt;Origen de los datos: Ministerio para la Transición Ecológica y el Reto Demográfico&lt;/em&gt;; Italian data as &lt;em&gt;Dati: Ministero delle Imprese e del Made in Italy (MIMIT) — Osservaprezzi Carburanti&lt;/em&gt;, under the Italian Open Data Licence 2.0. Station locations outside the priced registers come from OpenStreetMap under ODbL. Every source is licence-classified before it ships, and the ones that fail that check are the countries listed above — which is why they are absent rather than quietly included.&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=fuel_stations&lt;/code&gt;, and &lt;code&gt;/routing/route/saved/{id}/features&lt;/code&gt; with &lt;code&gt;types=fuel_stations&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 priced networks as they stand 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/per-station-fuel-prices-europe.html" rel="noopener noreferrer"&gt;napspan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>logistics</category>
      <category>api</category>
      <category>dataengineering</category>
      <category>opendata</category>
    </item>
    <item>
      <title>When the Season Turns, “Is the Road Passable” Has 21 Different Answers</title>
      <dc:creator>Roman Kotenko</dc:creator>
      <pubDate>Thu, 22 Oct 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/road511/when-the-season-turns-is-the-road-passable-has-21-different-answers-3jjp</link>
      <guid>https://dev.to/road511/when-the-season-turns-is-the-road-passable-has-21-different-answers-3jjp</guid>
      <description>&lt;p&gt;It is the middle of September and every winter feed we carry says the roads are fine. Illinois reports &lt;em&gt;clear&lt;/em&gt; on 9,639 segments. Alberta and Saskatchewan say &lt;em&gt;bare dry&lt;/em&gt;. California has 1,009 chain-control checkpoints live and every single one reads &lt;code&gt;R-0&lt;/code&gt; — no restriction in force. That is the correct answer today, and it is exactly why this is the right week to wire the layer, not the second week of November when a dispatcher is refreshing six DOT pages by hand at 5 a.m.&lt;/p&gt;

&lt;p&gt;Because from October the thing that ruins a run usually is not an incident. It is a &lt;em&gt;condition&lt;/em&gt;. Nothing is closed, nothing is crashed, there is no event to query — the road is simply covered, and the question is whether your driver should be on it. That question lives in a different dataset from the one your incident feed reads, and it is the least standardized dataset in North American road data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditions Are Not Incidents, and They Are Not in Your Events Feed
&lt;/h2&gt;

&lt;p&gt;An incident feed answers &lt;em&gt;what happened at this point&lt;/em&gt;. A conditions feed answers &lt;em&gt;what this stretch is like right now&lt;/em&gt; — which is a property of a segment, published on its own cadence, usually by a different part of the same agency. If you are reading only events, a corridor with 200 miles of snow-covered pavement and no crashes on it looks identical to a corridor in July.&lt;/p&gt;

&lt;p&gt;We carry that layer as &lt;code&gt;road_conditions&lt;/code&gt;. Here is what it actually holds, read from production on &lt;strong&gt;17 September 2026, 12:42–13:02 UTC&lt;/strong&gt; — a single snapshot, and a deliberately off-season one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;17,407 active condition segments across 21 states and provinces&lt;/strong&gt; — Illinois 9,246, Alberta 1,237, Wisconsin 1,175, North Dakota 750, Ontario 546, Montana 544, South Dakota 542, Alaska 496, Québec 446, Nevada 434, Arkansas 393, Manitoba 366, West Virginia 276, Idaho 237, Yukon 156, Newfoundland &amp;amp; Labrador 156, Utah 145, Colorado 100, New Brunswick 87, Northwest Territories 59, Washington 16.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7,608 of them carry a LineString&lt;/strong&gt;, so they are real stretches of road you can intersect with a route rather than pins on a map; 17,287 of 17,407 carry geometry of some kind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All 21 sources were polled within a 20-minute window&lt;/strong&gt; at that snapshot — oldest reading 12:42:05, newest 13:02:10. This is a live layer, not a nightly dump.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Six Field Names, Twenty-Seven Words, One Question
&lt;/h2&gt;

&lt;p&gt;Here is the part that makes this a normalization problem rather than a plumbing one. Twenty-one agencies describe the same physical thing, and they do not agree on &lt;em&gt;what the field is called&lt;/em&gt;, let alone what goes in it. Across the layer the condition text arrives under &lt;strong&gt;six different property names&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;Property key&lt;/th&gt;
&lt;th&gt;Who publishes it&lt;/th&gt;
&lt;th&gt;Segments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;condition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;19 of 21 sources&lt;/td&gt;
&lt;td&gt;16,216&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;conditions&lt;/code&gt; &lt;em&gt;(in addition to &lt;code&gt;condition&lt;/code&gt;)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Ontario, Colorado&lt;/td&gt;
&lt;td&gt;646&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;condition_category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;North Dakota&lt;/td&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;condition_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Québec&lt;/td&gt;
&lt;td&gt;446&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;surface_conditions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alaska (part of its rows)&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;road_condition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Washington&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;And inside those fields, &lt;strong&gt;27 distinct condition strings&lt;/strong&gt; across the 19 sources that publish one. Some of them are the same state of the world wearing four different coats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bare dry          1,275   (Alberta, Saskatchewan)
dry                 687   (3 sources)
bare and dry        309
3 - dry              75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Others are not descriptions at all. North Dakota publishes &lt;code&gt;Green&lt;/code&gt; — a colour on its own scale, 750 segments of it. Québec publishes &lt;code&gt;Ds&lt;/code&gt; and &lt;code&gt;Dm&lt;/code&gt;, 309 and 137 segments, which mean something precise to Transports Québec and nothing to a dispatcher in Indiana. Several agencies offer &lt;code&gt;good&lt;/code&gt;, &lt;code&gt;fair&lt;/code&gt; and &lt;code&gt;open&lt;/code&gt;, which are judgements rather than observations.&lt;/p&gt;

&lt;p&gt;We do not flatten these into a severity score. That is a deliberate decision and it is worth stating plainly: inventing a five-point scale would mean deciding that Ontario’s &lt;code&gt;normal_seasonal&lt;/code&gt; and Alberta’s &lt;code&gt;bare dry&lt;/code&gt; are the same number, and we have no basis for that beyond our own opinion. &lt;strong&gt;You get the publisher’s own words, in one schema, on one endpoint&lt;/strong&gt;, plus the jurisdiction that said them — so you can map the vocabulary to your own risk policy once, in one place, instead of writing 21 scrapers and then doing it anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Layer Says Today, Off-Season
&lt;/h2&gt;

&lt;p&gt;A conditions feed in September is mostly a feed reporting that it is September. That is the honest reading, and the distribution shows it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition, as published&lt;/th&gt;
&lt;th&gt;Segments&lt;/th&gt;
&lt;th&gt;Sources&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;clear&lt;/td&gt;
&lt;td&gt;9,639&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no report&lt;/td&gt;
&lt;td&gt;1,715&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bare dry&lt;/td&gt;
&lt;td&gt;1,275&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seasonal conditions&lt;/td&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dry&lt;/td&gt;
&lt;td&gt;687&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;normal_seasonal&lt;/td&gt;
&lt;td&gt;546&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting rows are the small ones, and they are all in the north, in September: &lt;strong&gt;24 segments already read &lt;code&gt;closed&lt;/code&gt;&lt;/strong&gt; — Manitoba 11, Northwest Territories 11, Alberta 2. Alaska publishes &lt;code&gt;no winter maintenance&lt;/code&gt; on 9. Yukon and Alaska carry &lt;code&gt;difficult&lt;/code&gt; on 13 between them, and the Northwest Territories &lt;code&gt;caution&lt;/code&gt; on 5. The season does not start everywhere on the same day, and the feed knows that before the calendar does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;no report&lt;/code&gt; is not &lt;code&gt;clear&lt;/code&gt;.&lt;/strong&gt; Those 1,715 segments across 8 sources mean nobody has filed an observation, which is a different fact from a road that has been looked at and found fine. Any rule you build on this layer has to treat the two separately, and we keep them separate rather than collapsing silence into good news.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chain Control Is the One That Is Actually Actionable — and California Is the Only State Proven in Winter
&lt;/h2&gt;

&lt;p&gt;Conditions tell you what the road is like. &lt;strong&gt;Chain control tells you what you are legally required to do about it&lt;/strong&gt;, which is the only part of winter data that changes a driver’s next ten minutes rather than a planner’s afternoon.&lt;/p&gt;

&lt;p&gt;We carry &lt;strong&gt;1,009 California chain-control checkpoints&lt;/strong&gt;, the only dedicated checkpoint feed in the set. Every one carries a complete record — &lt;code&gt;chain_status&lt;/code&gt;, &lt;code&gt;chain_control_active&lt;/code&gt;, &lt;code&gt;elevation&lt;/code&gt;, &lt;code&gt;county&lt;/code&gt; and &lt;code&gt;district&lt;/code&gt; are populated on all 1,009 rows, which is a fill rate almost nothing else in North American road data achieves. On 17 September every checkpoint read &lt;code&gt;R-0&lt;/code&gt;: no requirement in force.&lt;/p&gt;

&lt;p&gt;This is also the one winter type that &lt;strong&gt;surfaces on a route&lt;/strong&gt;. Ask for a truck route and an active chain control on it comes back as a warning in the response, keyed off &lt;code&gt;chain_control_active&lt;/code&gt;. The R-1/R-2/R-3 escalation on Donner Pass in January is a real operational signal and it is wired end to end.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;**Update, 15 October 2026:&lt;/em&gt;* this section first said chain control was one state. Since 11 October we also read the chain and traction requirement that &lt;strong&gt;Nevada, Idaho, Utah, Colorado and Washington&lt;/strong&gt; publish on their road-condition segments — NV 434, ID 237, UT 145, CO 100, WA 16, so &lt;strong&gt;1,941 rows across six states&lt;/strong&gt; with California. Every row carries &lt;code&gt;chain_control_active&lt;/code&gt;, and every row in the five new states keeps the operator’s own words in &lt;code&gt;source_restriction_text&lt;/code&gt;. Measured on 15 October, 16:02 UTC: &lt;strong&gt;0 active anywhere&lt;/strong&gt;.*&lt;/p&gt;

&lt;p&gt;Read that carefully before you lean on it. These five are not checkpoint feeds like California’s; they are a restriction field on the same segments listed above. Today they say “No Report”, “Dry”, “No restrictions” or a Colorado surface code, which proves only that the quiet case parses. &lt;strong&gt;Their winter wording has not been seen yet&lt;/strong&gt; — it cannot be until it snows — and a phrase that names neither chains, traction nor a known code would read as inactive. The text is kept on every row so you can check it yourself, and we will re-measure after the first active row outside California. Oregon is enabled and has published no rows yet. For I-70 west of Denver or I-90 over Snoqualmie this layer now carries the segment; it has not yet proven it carries the requirement.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;**Update, 22 October 2026 — the season has turned.&lt;/em&gt;* Measured 07:03 UTC. The first active row outside California arrived overnight: Nevada’s &lt;strong&gt;SR-431, the Mt. Rose Highway&lt;/strong&gt;, reads “Chains Required, All Vehicles except 4-wheel drive with Snow tires” and parses as active, so the Nevada wording is now proven for that phrase — Idaho, Utah, Colorado and Washington still are not. In California five checkpoints have left &lt;code&gt;R-0&lt;/code&gt; for &lt;code&gt;ROAD CLOSED&lt;/code&gt;: the seasonal closures over Sonora, Ebbetts and Monitor passes and the Tioga gate at Crane Flat. Those come back with &lt;code&gt;chain_control_active&lt;/code&gt; set, which is a closure carried on the chain field rather than a chain requirement — read &lt;code&gt;chain_status&lt;/code&gt; before you tell a driver to chain up. Across the conditions layer the vocabulary has grown from 27 distinct strings to &lt;strong&gt;37&lt;/strong&gt;, and Alaska now reports snow or ice on &lt;strong&gt;353&lt;/strong&gt; of its 497 segments. The September figures above stay as they were measured; this paragraph is the October reading.*&lt;/p&gt;

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

&lt;p&gt;One feature type, one endpoint, filtered the same way as everything else we carry:&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=road_conditions&amp;amp;
  jurisdiction=MT&amp;amp;
  limit=2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;type&lt;/code&gt; for &lt;code&gt;chain_controls&lt;/code&gt; to get the chain-control rows, drop &lt;code&gt;jurisdiction&lt;/code&gt; to sweep all 21, or pass a bounding box to take a corridor rather than a state. The response carries the publisher’s condition text in &lt;code&gt;properties&lt;/code&gt; under whichever of the six keys that agency uses, alongside the normalized fields — jurisdiction, geometry, &lt;code&gt;last_seen_at&lt;/code&gt; — that are the same for every type in the system. The &lt;code&gt;road_conditions&lt;/code&gt; and &lt;code&gt;chain_controls&lt;/code&gt; layers are on every plan, free trial included.&lt;/p&gt;

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

&lt;p&gt;Four of them, and as with everything else we publish they matter more than the headline count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wisconsin publishes no condition text at all.&lt;/strong&gt; Its 1,175 segments arrive with &lt;code&gt;segment&lt;/code&gt; and &lt;code&gt;area_name&lt;/code&gt; and nothing else — a list of stretches with no state attached to them. They are in the layer because the segment list itself is useful and because the field may start arriving; they are not in it because we can tell you what those roads are like. That is 1,175 of 17,407 rows, and a naive count of “segments covered” would quietly include them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Road conditions do not enrich a route.&lt;/strong&gt; Chain controls do; conditions do not. Ask for a route today and you will get work zones, hazards, weigh stations, border crossings and chain controls shaped into warnings — and no road-condition warning, because the spatial rule for “this 40-mile segment is snow-covered and your route uses eleven miles of it” is not the same rule as “there is an incident 60 metres from the line”, and we have not shipped it. Query the layer directly and intersect it yourself, or wait; do not assume the route response has already checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The plow layer exists, and its counts are not trustworthy yet.&lt;/strong&gt; We carry &lt;code&gt;service_vehicles&lt;/code&gt; from 13 jurisdictions and today 1,222 of them are active — Pennsylvania 767, Indiana 243, Iowa 115. The lifecycle on this type flaps: a vehicle that stops reporting is deactivated and reactivated repeatedly, so the active count is a reading of who is transmitting this minute and not a measure of a state’s fleet or its effort. It is under repair. Until it is fixed, treat plow positions as a live sighting, never as a denominator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything above is a September snapshot of a winter layer.&lt;/strong&gt; The counts will be different in January — that is the entire point of it being an endpoint rather than a table in a PDF. Re-run the queries and you will get the day you are standing in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Wire It in September
&lt;/h2&gt;

&lt;p&gt;The work of adopting a conditions layer is not the querying. It is deciding what your operation does when Québec says &lt;code&gt;Dm&lt;/code&gt;, what &lt;code&gt;no report&lt;/code&gt; means for a load you have already quoted, and which of the 27 strings are allowed to hold a driver at a truck stop. That is a policy conversation with your dispatch team, and it is a much better conversation to have in September, against a live feed that says everything is clear, than during the first real storm of the season.&lt;/p&gt;

&lt;p&gt;The feed is running now. Point it at your corridors and read a month of boring answers — that is how you find out what your rules should be before the answers stop being boring. 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; are this shape: the data exists, in 21 places, in 21 vocabularies, and the cost is never the fetching.&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, its jurisdiction and bounding-box filters, and the full feature-type list&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://road511.com/blog/weather-aware-dispatch-api.html" rel="noopener noreferrer"&gt;Weather at the incident&lt;/a&gt; — the observation layer that sits next to this one&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/winter-road-conditions-api.html?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=winter-road-conditions-api&amp;amp;utm_content=dev-14" rel="noopener noreferrer"&gt;road511.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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