The SuperNYC subway status page shows whether each of the 26 NYC subway routes is running normally, has delays, or is disrupted — updated every minute from the MTA. This page documents what feed we read, how we decide what status badge to show, what the badge does and does not promise, and what we explicitly do not cover.
The source feed
Our status data comes from MTA's GTFS-Realtime service alerts feed, the same feed that powers the official Service Status box on new.mta.info. The endpoints are:
- Protobuf (binary GTFS-RT):
https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/camsys%2Fsubway-alerts - JSON (same payload, server-decoded by MTA):
https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/camsys%2Fsubway-alerts.json
Both URLs serve the same logical payload. We use the JSON variant because the standard gtfs-realtime-bindings protobuf decoder drops the NYCT-specific Mercury extension fields (alert_type, sort_order, display_before_active) that the Service Status Box algorithm requires.
The feed is GTFS-Realtime v2.0 with incrementality: "FULL_DATASET", which means every fetch returns the current full set of alerts — we don't need to track per-alert diffs. header.timestampadvances roughly every 30-60 seconds in practice. The feed is served anonymously (no API key required) from MTA's CDN.
The Service Status Box algorithm
MTA published a specific algorithm for translating their alerts feed into a per-line status badge, documented at mta.info/developers/service-status-box (the live page returns 403 to most automated fetchers; we archive a snapshot of the algorithm spec in our docs). The algorithm, paraphrased:
- For each subway route (the 1, 2, 3, ..., L, G, plus the shuttles and SIR), find all alerts in the feed where the alert's
informed_entitylist contains that route id. - Filter those alerts to ones currently active: the alert's
active_period.startis in the past and eitheractive_period.endis absent or in the future. - If no alerts pass the filter, show "No Active Alerts" (per MTA's own recommended fallback text).
- If alerts pass the filter, sort them by
MercuryEntitySelector.sort_orderdescending. The Mercurysort_orderis encoded as a colon-delimited string like"MTASBWY:D:26"— agency, route, then a severity integer. The integer is what we sort on. - The top-sort-order alert's
MercuryAlert.alert_typebecomes the line's status.alert_typeis free-form editorial text from MTA (e.g., "Delays", "Planned - Suspended", "Station Notice").
Per MTA's spec, the "largest integer in sort_order" rule is what determines which alert wins when multiple are active on the same line. Alerts with higher severity (e.g., Suspended at sort_order 39) outrank lower-severity alerts (e.g., Delays at 22 or Slow Speeds at 16) and that's the one whose alert_type we display.
Mapping MTA alert_types to our status badges
MTA's alert_type field is free-form editorial text and not part of the GTFS-RT standard, so we collapse it into a six-bucket internal enum used for badge color and label. The mapping is documented exhaustively in the source file and surfaced here in summary:
- no-active-alerts(green) — nothing attached to the route, or only positive operational alerts like "Extra Service."
- planned-work (blue) —
Special Scheduleand similar planned-but-not- disruptive labels. - service-change (amber) —
Planned - Reroute,Planned - Stops Skipped,Planned - Express to Local,Reduced Service. - delays (orange) —
Delays,Severe Delays. - station-notice (amber) —
Station Notice,Boarding Change,Bypassing. - suspended (red) —
No Scheduled Service,Planned - Suspended,Planned - Part Suspended,Part Suspended.
When MTA introduces a new alert_typestring we haven't mapped, our implementation falls back to service-change(the safest "something is up" bucket) and fires a Sentry warning so we can update the mapping in the same change. We don't silently swallow unknowns — a new alert_type is a documentation update trigger.
What our badge does (and doesn't) promise
What "no active alerts" means
A green "no active alerts" badge means MTA hasn't published a service alert against that route in the time window we're reading. That isn't the same as "trains are running exactly on schedule." The MTA doc explicitly notes that minor service disruptions don't always have alerts attached, and we follow their recommendation to call this state "No Active Alerts" rather than "Good Service" to be accurate about what we're actually claiming.
Suspended and Part Suspended are the most severe
A red "Suspended" or "Part Suspended" badge means trains aren't running on at least part of the route. The header text on the alert (which we surface on the line drill-down page) typically specifies which segment. Suspended alerts win the sort_order race against anything else, so they'll be the badge state even when delays and station notices also exist on the same route.
What this tool does not do
- No countdown clocks."Next 1 train in 4 minutes" requires parsing per-station trip updates across seven separate GTFS-RT feeds. Citymapper, Transit App, and the official MyMTA app handle this well. Out of scope for the status-box use case.
- No trip routing."Should I take the F or the M to Bryant Park?" needs full graph routing over the static GTFS plus trip-update merge. Not the question this tool answers.
- No accessibility or elevator alerts. Elevator and escalator status lives in a separate MTA feed (
nyct/nyct_ene*.json) and isn't part of the service-alerts feed. Out of scope for v1. - No promise that the status reflects the platform experience exactly. MTA's alerts feed reflects MTA's editorial choices about what to publish. Riders sometimes experience delays that don't make it into the feed. The badge is what the MTA system says, not what every rider experiences.
Caching and fallback
We cache the feed in Vercel KV with a 60-second TTL. The page server-renders first paint from cache (which is also what search engines and AdSense crawlers see), and a small client component polls every 60 seconds to refresh the badges in-place.
If the upstream feed call fails, we serve the last good cached payload for up to 5 minutes, annotated with a small "last updated" timestamp so the user can see the staleness. Beyond 5 minutes stale, we stop serving "everything is fine" data and show a page-level "Service status temporarily unavailable" banner. The worst-case wrong answer for a transit-status tool is silently showing a green badge while a major incident is underway — we deliberately avoid that failure mode by failing visibly.
Re-verification schedule
- Feed URL anonymous fetchability— quarterly. If the feed ever starts returning 401/403, that triggers a re-research; we don't silently add an API key path.
- Service Status Box algorithm— annually, or whenever MTA revises the published doc.
- Route list + colors— on every MTA announcement of a route addition, removal, or recolor.
- alert_type observed value set— quarterly, plus on any Sentry-caught unknown_alert_type event from production.
Primary sources
- MTA service alerts feed: api-endpoint.mta.info camsys/subway-alerts
- MTA Service Status Box algorithm: mta.info/developers/service-status-box (Wayback snapshot 2025-02-06)
- GTFS-Realtime Reference for the New York City Subway: mta.info/document/134521
- Static GTFS feed (route IDs, colors): rrgtfsfeeds.s3.amazonaws.com/gtfs_subway.zip
- gtfs-realtime-bindings (npm): npmjs.com/package/gtfs-realtime-bindings
See live subway status for the live grid, or browse per-line status pages.