← Back to all posts
Trucking Cross-Border Routing

One Bridge, Two Border Waits — and Only One of Them Is Yours

August 8, 2026 · 8 min read

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.

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

DirectionPublisherCommercial waitReported at
Into the US (northbound plaza, Port Huron)U.S. Customs and Border Protection0“At Midnight EST”
Into Canada (Point Edward)Canada Border Services Agency25 minutes2026-08-08 02:22 EDT

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 -- at all 29 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.

Both Halves, One Field, On the Route

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

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

{
  "type": "border_crossing",
  "severity": "warning",
  "title": "Blue Water Bridge",
  "description": "Commercial border wait ~25 min.",
  "status": "open",
  "as_of": "2026-08-08T07:03:04Z",
  "source": "CBSA",
  "properties": {
    "commercial_wait_minutes": 25,
    "commercial_canada_bound": "25 minutes",
    "commercial_us_bound": "--",
    "location": "Point Edward, ON/Port Huron, MI",
    "last_updated": "2026-08-08 02:22 EDT"
  }
}
{
  "type": "border_crossing",
  "severity": "info",
  "title": "Port Huron - Bluewater Bridge",
  "description": "Commercial border wait ~0 min.",
  "status": "open",
  "as_of": "2026-08-08T07:10:43Z",
  "source": "CBP",
  "properties": {
    "commercial_wait_minutes": 0,
    "wait_trend": "steady",
    "hours": "24 hrs/day",
    "port_status": "Open",
    "commercial": {
      "delay_minutes": 0, "status": "no delay", "trend": "steady",
      "lanes_open": 2, "max_lanes": 9,
      "fast_status": "Lanes Closed", "update_time": "At Midnight EST"
    },
    "passenger": { "delay_minutes": 0, "lanes_open": 1, "max_lanes": 10 }
  }
}

One field means the same thing on both: commercial_wait_minutes, 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.

Note that there are two clocks here, and they are different clocks. as_of is our 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 properties: CBSA’s last_updated (“2026-08-08 02:22 EDT” above) and CBP’s update_time (“At Midnight EST”), each in the agency’s own wording and timezone. A fresh as_of 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 reading is rather than how old the fetch is, read the agency field.

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

The direction caveat, stated up front

We do not yet resolve which side of the crossing your truck is on. When both directions are known, the warning carries the larger of the two — 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 commercial_wait_minutes 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.

Two Vocabularies for One Number

The two agencies do not merely publish different directions — they publish different kinds of thing. 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 No Delay (21 offices), four actual figures (3 minutes, 5 minutes, 12 minutes, 25 minutes), Not Applicable (3), and Temporarily closed (1).

Normalizing that is the boring part of the job and the part that has to be right:

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 0 is how a dispatch board ends up confidently green in front of a two-hour queue. When nothing parseable is there, the warning stays info and says "Border crossing on route." — a crossing you should know about, with no wait claimed:

{
  "type": "border_crossing",
  "severity": "info",
  "title": "Sumas",
  "description": "Border crossing on route.",
  "status": "open",
  "as_of": "2026-08-08T07:05:42Z",
  "source": "CBP"
}

“No Delay” With Two Lanes Open

Look again at the Port Huron block: "lanes_open": 2, "max_lanes": 9. 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: 2 of 13 commercial lanes open, no delay. Buffalo’s Peace Bridge: 4 of 8, no delay.

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, lanes_open against max_lanes is the more interesting series of the two.

Closed Is a State, Not an Absence

In that snapshot, 27 of the 81 CBP ports were reported closed — 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 6 am-10 pm or 8 am-4 pm). Laredo’s World Trade Bridge, a commercial-only crossing, was shut: it was 2 a.m. in Texas and its published hours are 7 am-Midnight.

A closed port comes back as critical, with the closure in the title:

{
  "type": "border_crossing",
  "severity": "critical",
  "title": "Laredo - World Trade Bridge — closed",
  "description": "Border crossing reported closed.",
  "status": "closed",
  "as_of": "2026-08-08T07:00:15Z",
  "source": "CBP"
}

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.

How Much of It Is Actually Populated

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

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 — a border-wait API is mostly a “no figure right now” API, 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.

The wider inventory works the same way. We hold 199 active border crossings across North America; 110 of them — the 81 CBP ports and 29 CBSA offices — sit behind a live wait feed. The remaining 89 come from provincial and state 511 map layers — Manitoba, Saskatchewan, Alberta, Québec, New Brunswick, British Columbia, Newfoundland and California — and are location records: they tell you a crossing exists and where, and they publish no wait at all. They still ride your route as info, never with an invented number.

What We Don’t Do

The Same Field on the Other Side of the Atlantic

The schema is not North-America-shaped by accident. Our European deployment runs the identical border_crossing warning over Ukraine’s electronic queue at the EU external frontier, where commercial waits are measured in days rather than minutes — the same commercial_wait_minutes integer, rendered as “4 d 3 h” instead of “25 min”. If you run both continents, it’s one integration. That story is on the NAPSPAN blog.

Try It

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). All figures in this post come from a single snapshot of our production database taken shortly after 07:00 UTC on 8 August 2026 and were correct at that moment only — query the API for the live picture.

Put both sides of the border on your route

One call returns a truck-aware route plus every land border crossing on the corridor, with the commercial wait each agency publishes normalized to one integer, lane capacity underneath it, and closed ports raised to critical instead of quietly vanishing. Free 14-day trial. No credit card.

Get Free API Key Read the Docs