Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mtaapi.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through fetching real-time subway arrivals for a specific stop and route. You will initialize the MTA client, call mta.subway.arrivals(), and process the response to display upcoming trains in your application.

Prerequisites

  • mta-js installed in your project (npm install mta-js)
  • An MTA API key set as MTA_API_KEY in your environment
1

Find your stop ID

Every MTA subway stop has a unique stop ID. Stop IDs follow a letter-and-number format where the letter generally corresponds to the train line complex and the number identifies the specific station.A few examples to get you oriented:
Stop IDStation
A27Howard Beach–JFK Airport (A train)
R16Times Square–42 St (N/Q/R trains)
12096 St (1/2/3 trains)
D14Atlantic Av–Barclays Ctr (B/D/N/Q trains)
You can look up stop IDs in the MTA GTFS Static Data or reference the stops.txt file in the MTA’s GTFS feed. Pass the stop ID as a string exactly as it appears in those resources.
2

Call mta.subway.arrivals()

Import the MTA client and call mta.subway.arrivals() with the stopId and route you want to query.
import { MTA } from 'mta-js'

const mta = new MTA({ apiKey: process.env.MTA_API_KEY })

const data = await mta.subway.arrivals({
  stopId: 'A27',
  route: 'A',
})
Both stopId and route are required. The route value is the train letter or number as it appears on signage (e.g., 'A', '1', 'N', 'L').
3

Process the response

The response is an array of Arrival objects. Each entry includes the route and stop as structured objects, the predicted arrival time as an ISO 8601 string, a minutes field pre-computed for you, and optional destination / displayDirection fields for building human-readable UI.
for (const arrival of data) {
  const label = arrival.displayDirection ?? arrival.destination ?? arrival.direction
  console.log(`${arrival.route.shortName}${label}${arrival.minutes} min`)
}
Use displayDirection first (e.g. "toward 8 Av"), falling back to destination (the raw headsign), and finally the raw direction string.Example response:
[
  {
    "mode": "subway",
    "route": { "id": "A", "shortName": "A", "color": "#0039A6" },
    "stop": { "id": "A27", "name": "Howard Beach-JFK Airport", "displayName": "Howard Beach–JFK Airport" },
    "direction": "north",
    "destination": "Inwood-207 St",
    "displayDirection": "toward Inwood-207 St",
    "arrivalTime": "2026-05-29T18:37:00.000Z",
    "minutes": 4,
    "tripId": "AFA25GEN-A078-Sunday-00_000600_A..N03R",
    "realtime": true,
    "source": "mta-gtfs-rt"
  }
]
4

Handle empty results and errors

When no trains are scheduled—late at night or during a service gap—arrivals will be an empty array. Wrap your call in a try/catch and check for an empty result before rendering.
try {
  const data = await mta.subway.arrivals({ stopId: 'A27', route: 'A' })

  if (data.length === 0) {
    console.log('No upcoming arrivals at this stop.')
    return
  }

  for (const arrival of data) {
    // display each arrival
  }
} catch (error) {
  console.error('Failed to fetch arrivals:', error)
}

Complete example

The function below ties all the steps together into a reusable arrivals display function.
import { MTA } from 'mta-js'

const mta = new MTA({ apiKey: process.env.MTA_API_KEY })

interface ArrivalDisplay {
  direction: string
  minutesAway: number
  tripId?: string
}

async function getSubwayArrivals(
  stopId: string,
  route: string
): Promise<ArrivalDisplay[]> {
  const arrivals = await mta.subway.arrivals({ stopId, route })

  return arrivals
    .filter((a) => a.minutes >= 0)
    .map((a) => ({
      tripId: a.tripId,
      direction: a.displayDirection ?? a.destination ?? a.direction,
      minutesAway: a.minutes,
    }))
}

// Display arrivals at Howard Beach on the A train
const arrivals = await getSubwayArrivals('A27', 'A')

for (const arrival of arrivals) {
  console.log(
    `${arrival.direction} — arriving in ${arrival.minutesAway} min (trip ${arrival.tripId})`
  )
}
MTA real-time data updates roughly every 30 seconds. Poll on the same interval to keep your UI current without overloading the API.
// Refresh arrivals every 30 seconds
setInterval(async () => {
  const arrivals = await getSubwayArrivals('A27', 'A')
  renderArrivals(arrivals)
}, 30_000)
The direction field uses NYCT’s 'north' / 'south' values even on east-west lines. For the L train, mta-js also accepts rider-facing 'east' / 'west' aliases as query input and maps them to the underlying feed directions. For display, prefer displayDirection (e.g. "toward 8 Av") or destination over the raw direction string.
If your rider enters where they want to go rather than a compass direction, use mta.subway.direction() to resolve a destination like "Union Sq" into the north / south value, then pass it straight to mta.subway.arrivals({ ..., direction }).