# US Border Wait Times - CBP Crossings Ranked by Drive + Wait (`rolescout/borderscout-us-border-wait-times`) Actor

US border wait times for the 85 land ports of entry on the Mexican and Canadian borders, from CBP's published figures, ranked by all-in time: the drive from your point plus the wait in your lanes (standard, SENTRI/NEXUS, Ready, FAST), for cars, trucks or pedestrians. JSON/CSV, pay per query.

- **URL**: https://apify.com/rolescout/borderscout-us-border-wait-times.md
- **Developed by:** [Dave Fergins](https://apify.com/rolescout) (community)
- **Categories:** Travel, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 query answereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

US border wait times as plain JSON or CSV rows, for all 85 US land ports of entry on the Mexican and Canadian borders, ranked by all-in time from your starting point: the drive there plus the current wait once there, per lane type, for passenger, commercial or pedestrian traffic. The waits are US Customs and Border Protection's own published figures. The nearest crossing is often not the fastest: a crossing 50 km further out with a 10-minute wait beats the one next door at 125 minutes, and only an all-in number shows it.

The Actor is a thin client: it sends each query you give it to the border wait service, charges one event per query the service answers, and writes the answer to the dataset as one summary row plus one row per crossing, fastest all-in first. A run takes seconds and uses 256 MB. You need no API key or account with any data provider; the Actor carries its own credential.

### Border crossings ranked by drive time plus the live CBP wait

One query is a starting point (`lat`, `lon`), a `vehicle` type (passenger, commercial or pedestrian; each has its own lanes and waits) and the `lanes` you may use: standard only by default, plus SENTRI/NEXUS, Ready and FAST when you hold them. Each crossing row carries `port`, `crossing` (the crossing's own name when the port has several), `port_number`, `border` (Mexican or Canadian), `port_status`, `hours`, `distance_km` (straight-line from your point), `drive_minutes` (at the assumed `speed`, default 70 km/h), the best usable lane flattened as `best_lane_lane`, `best_lane_status`, `best_lane_delay_minutes`, `best_lane_lanes_open`, `best_lane_update_time` and `best_lane_reported`, and `total_minutes`, which is `drive_minutes` plus the best lane's wait and the ranking key. The full per-lane list stays nested in `lanes`, and `construction_notice` appears when CBP posts one.

`best_lane_reported` matters: CBP reports nothing at all for some lanes, and those are shown as unreported (false) rather than as a zero-minute wait. `radius` limits how far to look in km (0 or empty: no limit) and `limit` how many crossings to return (default 5).

### Several starting points in one run

The top-level fields are the first query and the defaults for every other. Add `points` as a list of "lat,lon" strings for more starting points with the same settings, or `queries` as a list of objects using the same field names for anything else, for instance a commercial query with the FAST lane next to a passenger one. A `queries` object with its own point does not inherit the top-level one; one that only changes a setting runs from the top-level point. A run may hold at most 25 queries, and each query the service answers is one billable event.

### Example input

```json
{
    "lat": 32.7157,
    "lon": -117.1611,
    "vehicle": "passenger",
    "lanes": ["standard", "SENTRI/NEXUS"],
    "radius": 0,
    "speed": 70,
    "limit": 5,
    "place": "San Diego",
    "points": ["32.5422,-116.9735"],
    "queries": [{ "lat": 31.7619, "lon": -106.485, "vehicle": "commercial", "lanes": ["standard", "FAST"], "place": "El Paso" }]
}
```

That run is three queries: passenger traffic from downtown San Diego with a SENTRI/NEXUS card, the same from Chula Vista, and commercial traffic from El Paso with FAST. Field names are the service's own, so a `queries` object is exactly the query it sends.

### Output: example rows, CSV views and the OUTPUT record

Every row starts with the same eight columns: `scout`, `row_type` (summary or crossing), `query_index` (1-based position in the run), `query` (the normalised query as sent), `as_of` (CBP's feed date and time), `receipt`, `source` and `disclaimer`. Then the row's own fields. A crossing row looks like this:

```json
{
    "scout": "crossings",
    "row_type": "crossing",
    "query_index": 1,
    "query": { "lanes": ["standard", "SENTRI/NEXUS"], "lat": 32.7157, "limit": 5, "lon": -117.1611, "place": "San Diego", "vehicle": "passenger" },
    "as_of": "10/8/2026 13:00:00",
    "receipt": "",
    "source": "U.S. Customs and Border Protection border wait times (bwt.cbp.gov), public domain",
    "disclaimer": "Wait times are US Customs and Border Protection's own published figures ...",
    "rank": 1,
    "port": "Otay Mesa",
    "crossing": "",
    "port_number": "250601",
    "border": "Mexican Border",
    "port_status": "Open",
    "hours": "24 hrs/day",
    "distance_km": 22.9,
    "drive_minutes": 20,
    "total_minutes": 25,
    "best_lane_lane": "SENTRI/NEXUS",
    "best_lane_status": "no delay",
    "best_lane_delay_minutes": 5,
    "best_lane_lanes_open": 3,
    "best_lane_update_time": "At 1:00 pm PDT",
    "best_lane_reported": true,
    "lanes": [{ "lane": "standard", "status": "delay", "delay_minutes": 25, "lanes_open": 10, "update_time": "At 1:00 pm PDT", "reported": true }],
    "vehicle": "passenger"
}
```

The summary row for each query carries `vehicle`, `eligible_lanes` (the lanes ranked on), `crossings_count`, `crossings_without_coordinates` (ports skipped because their location is unknown) and the plain-language `summary`. The dataset has two table views: `summary` (one line per query) and `crossings` (one line per crossing). Filter on `row_type` when you export both as one CSV. The run's `OUTPUT` key-value record holds the service's response for every query verbatim, keyed q1, q2, ..., each with the query it answered, plus an `errors` map for any query that failed.

### Pricing: pay per query answered

One `query-answered` event per query the service answered, whatever the number of crossings: one starting point is one event, ten is ten events. Rows are never billed individually, so `limit` changes the size of the dataset, not the price. A query the service refuses (the service warming up, a malformed lane list) is not charged, and the run still succeeds with the queries that worked; only a run where every query failed ends as failed. A query with no open crossing in range is answered, charged once and writes its summary row saying so. The per-event price is on this page's Pricing tab. A run every 15 minutes for one starting point is about 2,900 events a month; hourly is about 720.

### Use cases: commuter and trucking dispatch, travel apps and dashboards

- Commuters and cross-border workers: which crossing to head for right now, counting the drive, with the SENTRI/NEXUS or Ready lane when you hold one.
- Trucking and dispatch: commercial lanes with FAST, ranked by the all-in time from the yard, on a schedule.
- Travel and logistics apps: the per-lane waits for the 85 ports in one flat table, with CBP's own update time on every row.
- Spreadsheets and dashboards: export the `crossings` view as CSV or attach Apify's Google Sheets integration so every scheduled run appends its rows and the wait history builds itself.
- AI agents and automations: like any public Actor it can be called through the Apify API, a webhook or Apify's MCP server, with the same input and the same dataset.

### Data source and attribution

The waits are U.S. Customs and Border Protection's published border wait times (bwt.cbp.gov), public domain, covering 85 US ports of entry (30 on the Canadian border, 55 on the Mexican border). Every row carries the attribution in `source` and the service's disclaimer verbatim:

> Wait times are US Customs and Border Protection's own published figures and can change sharply within minutes; CBP reports nothing at all for some lanes, and those are shown as unreported rather than as zero. Drive times are straight-line estimates at an assumed average speed, not routed directions. Confirm at the crossing.

### FAQ

**Which crossings are covered?** The 85 US land ports of entry CBP publishes wait times for: 55 on the Mexican border and 30 on the Canadian border. Ports whose location CBP does not publish are skipped and counted in `crossings_without_coordinates`.

**How fresh are the waits?** They are CBP's own figures as published at `as_of`, with each lane's `update_time` in CBP's words. They can change sharply within minutes, so a schedule is the way to track them.

**Is the drive time a routed estimate?** No. `distance_km` is straight-line and `drive_minutes` divides it by the assumed `speed` (default 70 km/h). It is good enough to rank crossings against each other, not a navigation estimate.

**What are the lane types?** standard (everyone), SENTRI/NEXUS (trusted-traveller programmes), Ready (RFID-enabled documents) and FAST (commercial trusted-shipper). List the ones you may use in `lanes`; the ranking uses the best usable lane at each crossing.

**What does reported = false mean?** CBP published no wait for that lane at that time. The row says so instead of showing a zero, so an unreported lane never looks like an empty one.

**Can I check several starting points in one run?** Yes: `points` for more "lat,lon" starting points with the same settings, or `queries` for a list of full query objects. At most 25 queries per run, one event each.

**Do I need an API key?** No. The Actor carries its own credential for the border wait service; you only give it the queries.

**Why did one query fail while the others worked?** The service answers each query on its own; a query it cannot answer is recorded in the status message and the `OUTPUT` record's `errors` map, skipped without charge, and the rest continue.

# Actor input Schema

## `lat` (type: `number`):

Latitude of the starting point in decimal degrees. Required.

## `lon` (type: `number`):

Longitude of the starting point in decimal degrees. Required.

## `vehicle` (type: `string`):

Each has its own lanes and waits.

## `lanes` (type: `array`):

Standard only when empty. Pick the trusted-traveller lanes you hold: SENTRI/NEXUS, Ready (RFID-enabled documents), FAST (commercial).

## `radius` (type: `number`):

Only consider crossings within this many km of the point. 0 or empty: no limit.

## `speed` (type: `number`):

Average speed for the straight-line drive estimate. The service's own default is 70.

## `limit` (type: `integer`):

How many crossings to return per query, fastest all-in first. It changes the dataset size, not the price.

## `place` (type: `string`):

Optional label used in the summary text, e.g. San Diego.

## `points` (type: `array`):

Extra "lat,lon" starting points, one query each, with the fields above as defaults. Each is one billable event; at most 25 queries per run.

## `queries` (type: `array`):

A list of query objects using the same field names as above, each merged over the top-level fields: mix traffic types, lanes and points in one run. At most 25 queries per run in total.

## Actor input object example

```json
{
  "lat": 32.7157,
  "lon": -117.1611,
  "vehicle": "passenger",
  "lanes": [
    "standard",
    "SENTRI/NEXUS"
  ],
  "radius": 200,
  "speed": 70,
  "limit": 5,
  "place": "San Diego",
  "points": [
    "31.7619,-106.4850",
    "49.2827,-123.1207"
  ],
  "queries": [
    {
      "lat": 31.7619,
      "lon": -106.485,
      "vehicle": "commercial",
      "lanes": [
        "standard",
        "FAST"
      ],
      "place": "El Paso"
    }
  ]
}
```

# Actor output Schema

## `rows` (type: `string`):

Summary and crossing rows for every query, with the query object and the full per-lane list on each crossing.

## `summary` (type: `string`):

One line per query. Filter on row_type = summary.

## `crossings` (type: `string`):

One line per crossing, fastest all-in first per query, with the best lane flattened. Filter on row_type = crossing.

## `answers` (type: `string`):

The service's response for each query verbatim, keyed q1, q2, ..., plus an errors map for any query that failed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "lat": 32.7157,
    "lon": -117.1611,
    "vehicle": "passenger",
    "lanes": [
        "standard",
        "SENTRI/NEXUS"
    ],
    "limit": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("rolescout/borderscout-us-border-wait-times").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "lat": 32.7157,
    "lon": -117.1611,
    "vehicle": "passenger",
    "lanes": [
        "standard",
        "SENTRI/NEXUS",
    ],
    "limit": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("rolescout/borderscout-us-border-wait-times").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "lat": 32.7157,
  "lon": -117.1611,
  "vehicle": "passenger",
  "lanes": [
    "standard",
    "SENTRI/NEXUS"
  ],
  "limit": 5
}' |
apify call rolescout/borderscout-us-border-wait-times --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rolescout/borderscout-us-border-wait-times"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KiG6onYbkMsWAvfzm/builds/lgYS7hWHwjhhDk5UX/openapi.json
