Live NYC subway arrival times as JSON, straight from the MTA's real-time
GTFS feeds. One GET request — that's the whole integration.
Base URL: https://py-nycmta.web.app
curl "https://py-nycmta.web.app/arrivals/F/F24?direction=N&count=3"
There is no sign-up, token, or key of any kind: every endpoint is public and CORS-enabled, so you can call it from a terminal, a server, or directly from browser JavaScript on any site.
Try it live
Endpoints
GET/health
Liveness check. Always returns 200 with {"status": "ok"}.
GET/arrivals/{line}/{stop}
Upcoming arrivals for one subway line at one stop, sorted soonest-first.
| Parameter | In | Description |
|---|---|---|
line |
path | One of the 22 supported lines, e.g. F or 6. Case-insensitive. Unknown lines return 400. |
stop |
path | 3-character base GTFS stop ID without a direction suffix, e.g. F24 (7 Av). Full list on the stations page. Unknown or mismatched stop IDs return 200 with an empty arrivals list, not an error. |
direction |
query | N (northbound), S (southbound), or both. Default both. Anything else returns 422. |
count |
query | Optional. Limit the number of arrivals returned; must be an integer > 0, otherwise 422. Omit for all upcoming arrivals. |
Response
| Field | Type | Description |
|---|---|---|
line | string | The requested line, uppercased. |
stop | string | The stop ID exactly as you sent it. |
direction | string | The direction filter that was applied. |
arrivals[] | array | Upcoming arrivals, sorted by arrival time. Empty if no trains are due. |
arrivals[].train_id | string | Line identifier of the arriving train. |
arrivals[].minutes_away | integer | Whole minutes until arrival. The most reliable field for countdowns. |
arrivals[].direction | string | "N" or "S". |
arrivals[].arrival_time | string | Naive ISO 8601 timestamp, e.g. 2026-06-09T14:32:00. No timezone offset (UTC on the hosted API) — prefer minutes_away. |
arrivals[].status | string | Human-readable countdown, e.g. "4 mins". |
Example
{
"line": "F",
"stop": "F24",
"direction": "N",
"arrivals": [
{
"train_id": "F",
"minutes_away": 4,
"direction": "N",
"arrival_time": "2026-06-09T14:32:00",
"status": "4 mins"
},
{
"train_id": "F",
"minutes_away": 11,
"direction": "N",
"arrival_time": "2026-06-09T14:39:00",
"status": "11 mins"
}
]
}
Errors
| Status | When | Body |
|---|---|---|
400 |
Unknown line | {"detail": "<message listing valid trains>"}. Note: the message may mention S and SIR, but those are not queryable here. |
422 |
Invalid direction or count |
Standard FastAPI validation error body. |
502 |
Upstream MTA feed failure or timeout | {"detail": "Failed to fetch MTA feed"}. Usually transient — retry after a moment. |
200 |
Unknown stop ID, stop not on that line, or simply no trains due | A normal response with "arrivals": [] — not a 404. Validate stop IDs against the stations list if you need to distinguish. |
No rate limits are enforced — please poll courteously (the underlying MTA feeds update roughly every 30 seconds, so there's nothing to gain from hammering). Responses are live and uncached; expect a typical latency of 1–3 seconds while the MTA feed is fetched.
Supported lines
All 22 lettered and numbered lines are supported. The S shuttles (42 St, Franklin Av, Rockaway Park) and the Staten Island Railway are not available through this API.
Examples
curl
# Next 3 northbound F trains at 7 Av
curl "https://py-nycmta.web.app/arrivals/F/F24?direction=N&count=3"
# Everything due at Times Sq-42 St on the N, both directions
curl "https://py-nycmta.web.app/arrivals/N/R16"
Python
import httpx
resp = httpx.get(
"https://py-nycmta.web.app/arrivals/F/F24",
params={"direction": "N", "count": 3},
)
resp.raise_for_status()
for arrival in resp.json()["arrivals"]:
print(f"{arrival['train_id']} train in {arrival['status']}")
JavaScript
const res = await fetch(
"https://py-nycmta.web.app/arrivals/F/F24?direction=N&count=3"
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { arrivals } = await res.json();
arrivals.forEach((a) => console.log(`${a.train_id} train in ${a.status}`));
Works from any origin — CORS is enabled for GET across the
whole API.
More
- Station codes — searchable list of all 496 stop IDs
- Swagger UI · ReDoc · OpenAPI spec
- Source on GitHub
- py-nycmta on PyPI — use the same data natively in Python, no HTTP hop