Skip to content
Docs

Bus positions

Our archive of the MTA's live bus positions, about one report per bus every 30 seconds, since October 2026.

Rows
7,822,245
Size
126 MB
Covers
2026-10-02 onward
Updated
Daily
Source
MTA bus GTFS-realtime vehicle positions
License
CC BY 4.0
Fileshttps://data.transitlab.nyc/v1/bus_vehicle_positions/year=YYYY/month=MM/YYYY-MM-DD.parquetOne file a day, 3 so far. In the database as transitlab.bus_vehicle_positions.

Rows and size from catalog.json, 2026-10-06.

One change to one bus. We fetch the MTA’s bus position feed twice a minute and keep a row only when something changes: a bus appears, reports a new position, or drops out of the feed. That keeps the table far smaller than every snapshot would be, with nothing lost.

Every row has an event. The same scheme is used by bus predictions, service alerts and the elevator tables.

eventMeaning
baselineThe bus as seen in the first snapshot of each UTC hour. Every hour starts with a full set, so you can read any hour on its own.
addedThe bus appeared in the feed.
changedIt reported something new (here: a new vehicle_ts).
removedIt dropped out of the feed. The row repeats its last known state.

To rebuild the feed as it stood at a moment, take each bus’s latest row at or before that time and drop buses whose latest row is removed. For speeds and paths, use vehicle_ts (when the bus reported), not observed_at (when we fetched it).

-- Where were M15 buses between 8 and 9 am on 2026-10-06?
SELECT vehicle_id, vehicle_ts, lat, lon, direction_id
FROM transitlab.bus_vehicle_positions
WHERE route_id = 'M15'
AND vehicle_ts >= TIMESTAMP '2026-10-06 12:00:00' -- 8 am in New York
AND vehicle_ts < TIMESTAMP '2026-10-06 13:00:00'
AND event <> 'removed'
QUALIFY row_number() OVER (PARTITION BY vehicle_id, vehicle_ts ORDER BY observed_at) = 1
ORDER BY vehicle_id, vehicle_ts;

Each day is its own file, so one day reads fast straight from its URL:

SELECT route_id, count(DISTINCT vehicle_id) AS buses
FROM 'https://data.transitlab.nyc/v1/bus_vehicle_positions/year=2026/month=10/2026-10-06.parquet'
GROUP BY route_id
ORDER BY buses DESC;
row_idvarchar

Stable ID of the row. If a row appears twice, keep one.

observed_attimestamp

When we fetched the feed snapshot this row came from (UTC).

eventvarchar

baseline, added, changed or removed. See reading change events.

vehicle_idvarchar

Bus ID as the feed gives it, e.g. MTA NYCT_8721.

vehicle_tstimestamp

When the bus reported this position (UTC).

trip_idvarchar

GTFS trip.

route_idvarchar

MTA route.

direction_idinteger

GTFS direction, 0 or 1.

start_datevarchar

Trip start date, YYYYMMDD.

latdouble

Latitude.

londouble

Longitude.

bearingdouble

Heading in degrees.

stop_idvarchar

Stop the bus is at or heading to.

  • A report can repeat at the top of each hour, once as baseline and once as the change before it. The QUALIFY line above keeps one per report.
  • Files are split by the UTC day of observed_at.
  • Bus speeds explains how we turn positions into speeds; bus route speeds has the result.