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.
mta-js uses MTA’s standard identifiers for stops and routes. These identifiers come directly from MTA’s GTFS static dataset—the same dataset that powers Google Maps, Apple Maps, and most third-party transit apps. Understanding how stop IDs, route IDs, and transit modes work is essential for querying the right data and interpreting the responses you receive.
Stop IDs
Every subway and bus stop in the MTA system has a unique stop ID defined in MTA’s GTFS static feed. Stop IDs are alphanumeric strings that identify a specific physical platform or stop location.
For subway stops, the ID typically combines a letter prefix and a numeric suffix. For example, A27 identifies the Howard Beach–JFK Airport station on the A line. The same physical station can have multiple stop IDs if it serves multiple lines or has separate platforms for each direction.
// Fetch arrivals at Howard Beach (A line, southbound platform)
const arrivals = await mta.subway.arrivals({
stopId: 'A27',
route: 'A'
})
stopId and route parameters across the SDK are typed as smart-string unions (e.g. SubwayStopId, SubwayRoute) — you get autocomplete for known IDs without being locked out of new or experimental values. See Typed route and stop IDs below.
You can look up stop IDs in MTA’s official GTFS reference, available at api.mta.info. The stops.txt file in the GTFS static download contains every stop ID, name, and geographic coordinate in the system.
Bus stop IDs follow a similar convention and are found in the same GTFS static dataset, in the stops.txt file.
Route IDs
Route IDs identify a specific subway line or bus route. mta-js uses these identifiers in method parameters to filter results to a single route.
Subway route IDs match the line letter or number you see on signage and subway maps:
| Route | Description |
|---|
A, C, E | A/C/E lines (8th Avenue) |
B, D, F, M | B/D/F/M lines (6th Avenue) |
1, 2, 3 | 1/2/3 lines (7th Avenue) |
L | L line (Canarsie) |
G | G line (Crosstown) |
Bus route IDs use the MTA route designation, which includes a borough prefix and route number:
| Route | Description |
|---|
B63 | B63 bus (Brooklyn, 5th Avenue) |
M15 | M15 bus (Manhattan, 1st/2nd Avenue) |
Q58 | Q58 bus (Queens) |
Bx12 | Bx12 bus (Bronx, Pelham Pkwy) |
route parameters across the SDK are typed as smart-string unions. Subway-specific methods narrow to SubwayRoute, bus-specific methods to BusRoute, and pan-mode methods (alerts, stops) accept RouteId. You get autocomplete for valid IDs without losing the freedom to pass any string. See Typed route and stop IDs below.
// Subway: filter to a specific route — `route` is SubwayRoute
const subwayArrivals = await mta.subway.arrivals({ stopId: '120', route: '1' })
// Bus: get vehicle positions for a route — `route` is BusRoute
const busPositions = await mta.bus.vehicles({ route: 'M15' })
Transit modes and directions
type TransitMode = 'subway' | 'bus' | 'lirr' | 'metro-north'
type Direction = 'north' | 'south' | 'east' | 'west' | 'unknown'
Methods that scope to a specific system accept a mode (or modes) parameter. The two most commonly used values are:
'subway' — the NYC subway system
'bus' — the NYC bus network (local, express, and SBS routes)
'lirr' and 'metro-north' are also valid for service alerts. mta.subway.arrivals additionally accepts 'uptown' and 'downtown' as direction aliases — they’re normalized into the underlying Direction value for you.
// Fetch only subway service alerts
const subwayAlerts = await mta.alerts.current({ mode: 'subway' })
// Fetch stops near a location, searching both subway and bus
const nearby = await mta.stops.near({
lat: 40.6501,
lon: -73.9496,
modes: ['subway', 'bus']
})
Timestamps
Time values in mta-js responses are returned as ISO 8601 strings (e.g. '2026-05-26T17:12:29.787Z'), so you can pass them straight to new Date() without unit conversion.
const { arrivals } = await mta.subway.arrivals({ stopId: 'A27', route: 'A' })
for (const arrival of arrivals) {
const arrivalDate = new Date(arrival.arrivalTime)
console.log(`${arrival.route.id} arrives at ${arrivalDate.toLocaleTimeString()} (in ${arrival.minutes} min)`)
}
Each Arrival also includes a precomputed minutes field for the common “minutes until arrival” use case, so you usually don’t have to do the math yourself.
TypeScript interfaces
mta-js ships with full TypeScript type definitions. Every response is a structured object — routes and stops are nested, not flat strings.
interface Route {
id: string
shortName?: string
longName?: string
color?: string
textColor?: string
type?: number
}
interface Stop {
id: string
name: string
lat?: number
lon?: number
parentStation?: string
mode?: TransitMode
}
type NearbyStop = Stop & {
distanceMeters?: number
servedRoutes?: Route[]
routeMatch?: boolean
routeHeadsigns?: string[]
note?: string
}
interface Arrival {
mode: TransitMode
route: Route
stop: Stop
direction: Direction
headsign?: string
arrivalTime: string // ISO 8601 timestamp
departureTime?: string // ISO 8601 timestamp
minutes: number // Minutes until arrival
tripId?: string
realtime: boolean
source: 'mta-gtfs-rt' | 'mta-bustime'
raw?: unknown // Present when includeRaw: true
}
interface Vehicle {
mode: TransitMode
route: Route
vehicleId?: string
tripId?: string
stop?: Stop // Next or current stop
lat?: number
lon?: number
bearing?: number // Degrees clockwise from north
destinationName?: string
recordedAt?: string // ISO 8601 timestamp
source: 'mta-bustime'
raw?: unknown
}
interface Alert {
id: string
mode?: TransitMode
routes: Route[]
stops: Stop[]
header?: string
description?: string
url?: string
effect?: string
severity?: string
activePeriods: { start?: string; end?: string }[]
source: 'mta-gtfs-rt'
raw?: unknown
}
Query parameter shapes:
interface SubwayArrivalQuery {
stopId: SubwayStopId
route?: SubwayRoute
direction?: Direction | 'uptown' | 'downtown'
limit?: number
includeRaw?: boolean
}
interface BusArrivalQuery {
stopId: BusStopId
route?: BusRoute
limit?: number
includeRaw?: boolean
}
interface BusVehicleQuery {
route?: BusRoute
vehicleId?: string
limit?: number
includeRaw?: boolean
}
interface AlertQuery {
mode?: TransitMode
route?: RouteId
stopId?: StopId
includeRaw?: boolean
}
interface StopsNearQuery {
lat: number
lon: number
modes?: TransitMode[]
route?: RouteId
includeRoutes?: boolean
radiusMeters?: number
limit?: number
}
All types are exported from mta-js for use in your own annotations:
import type {
Arrival,
Alert,
Vehicle,
Stop,
NearbyStop,
Route,
TransitMode,
Direction,
} from 'mta-js'
Typed route and stop IDs
mta-js exports literal union types generated from the live hosted stops snapshot, plus “smart string” wrappers that let you pass any string while still getting autocomplete for known values. Every stopId and route parameter on the SDK uses these wrappers, so your editor suggests valid IDs as you type — but you can still pass new or experimental IDs without fighting the type system.
import type {
// Smart-string types used by SDK params (autocomplete + accept any string)
RouteId,
SubwayRoute,
BusRoute,
StopId,
SubwayStopId,
BusStopId,
// Underlying generated literal unions (strict)
KnownRoute,
KnownSubwayRoute,
KnownBusRoute,
KnownStopId,
KnownSubwayStopId,
KnownBusStopId,
} from 'mta-js'
| Type | Definition | Use for |
|---|
RouteId | `KnownRoute | Any route ID (subway + bus). |
SubwayRoute | `KnownSubwayRoute | Subway-only route IDs. |
BusRoute | `KnownBusRoute | Bus-only route IDs. |
StopId | `KnownStopId | Any stop ID. |
SubwayStopId | `KnownSubwayStopId | Subway-only stop IDs. |
BusStopId | `KnownBusStopId | Bus-only stop IDs. |
The (string & {}) intersection is the TypeScript trick that preserves autocomplete for the known literals while still allowing any string at the type level.
import { MTA } from 'mta-js'
const mta = new MTA({ apiKey: process.env.MTA_API_KEY })
// Autocomplete suggests 'L', 'L08', etc., but unknown strings are also accepted
await mta.subway.arrivals({ stopId: 'L08', route: 'L' })
await mta.bus.vehicles({ route: 'M23-SBS' })
If you want stricter compile-time enforcement that rejects any value not in the snapshot, annotate against the Known* unions directly:
import type { KnownSubwayRoute, KnownSubwayStopId } from 'mta-js'
async function getArrivals(stopId: KnownSubwayStopId, route: KnownSubwayRoute) {
return mta.subway.arrivals({ stopId, route })
}
await getArrivals('L08', 'L') // ✓ valid
// await getArrivals('L08', 'XX') // ✗ TS error: not assignable to KnownSubwayRoute
These literal unions are regenerated from the hosted snapshot at mtaapi.dev, so they always match the routes and stops the API currently knows about. Update mta-js to pick up newly added routes or stops.
Prefer the narrowest type that matches your call site — KnownSubwayRoute over KnownRoute when you’re only working with subway, etc. The narrower the type, the more autocomplete and the fewer runtime surprises.