GTFS-RT feeds
Use GTFS-RT if your system already consumes GTFS Realtime. For rider apps and live streams, Frontline is recommended.
Both feeds are at GTFS_BASE_URL and need Authorization: Bearer ACCESS_TOKEN
with the frontline:consumer scope. See the feed reference
for the route inventory.
| Request | Returns |
|---|---|
GET /gtfs-rt/trip-updates |
GTFS Realtime FeedMessage with trip updates |
GET /gtfs-rt/vehicle-positions |
GTFS Realtime FeedMessage with vehicle positions |
Responses are binary Protocol Buffers (application/x-protobuf), GTFS Realtime
version 2.0, with FULL_DATASET incrementality: replace your view with each
snapshot instead of applying it as a delta.
These are not static GTFS, JSON, or alerts feeds. Decode them with your language’s GTFS Realtime library and match them against the static GTFS identifiers from your MyBusz contact; this site has no static dataset download.
Download a feed
Section titled “Download a feed”curl --fail --silent --show-error \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --dump-header trip-updates.headers \ --output trip-updates.pb.download \ --write-out 'HTTP %{response_code}\n' \ "${GTFS_BASE_URL}/gtfs-rt/trip-updates"Accept the file only if the transfer completed with HTTP 200, the content type
is application/x-protobuf, and it decodes as a FeedMessage. Downloading to a
separate file means a failed attempt can’t overwrite good data. Use --fail, not
--fail-with-body, so an error body is never saved as a feed, and don’t treat a
redirect or partial transfer as a feed. Check the feed header and entity
timestamps before showing data as current.
Conditional requests
Section titled “Conditional requests”200 and 304 responses include:
| Header | Meaning |
|---|---|
ETag |
Weak validator (W/"…") based on feed content, not a byte checksum |
Last-Modified |
When the feed artifact was generated, not each vehicle’s observation time |
Cache-Control: private, no-store |
Don’t store the response in any HTTP or shared cache |
Send the last ETag back exactly, including quotes and any W/ prefix, as
If-None-Match:
curl --fail --silent --show-error \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --header "If-None-Match: ${FEED_ETAG}" \ --dump-header trip-updates.headers \ --output trip-updates.pb.download \ --write-out 'HTTP %{response_code}\n' \ "${GTFS_BASE_URL}/gtfs-rt/trip-updates"200: decode the new body, then update your data and storedETag.304 Not Modified: no body; keep your current data. The feed content is unchanged, which doesn’t mean no vehicle has moved.- With no usable earlier data, make an unconditional request.
Rely on If-None-Match, not If-Modified-Since. An unchanged feed can keep its
ETag even when timestamps or encoded bytes differ.
Freshness
Section titled “Freshness”A missing or stale feed returns 503 Service Unavailable, not an empty feed,
even when your ETag matches. A feed is stale after 300 seconds by default
(configurable per feed). That is the artifact’s age, not a guaranteed delivery
latency.
Entities are dropped when observations or trip matches are unusable, and vehicle
positions can lack trip details, so a 200 doesn’t prove full fleet coverage. If
you keep showing data during an outage, label it as stale.
Polling
Section titled “Polling”You get 10 requests per minute per feed for each identity, whatever your
credential type, and the two feeds have separate budgets. 304 responses count.
Poll each feed about every 10 seconds, one request at a time, and share the
budget across all workers using the same identity. A 304 is normal; don’t poll
faster because of it.
See rate limits and retry and backoff.