# MBTA Boston: Bus & Train Arrivals, Timetables, Alerts (`yadroo/mbta-arrivals`) Actor

Next departures at any MBTA stop or station as rows: minutes away, headsign, direction, live flag and delay against the timetable. Slots the source does not predict are filled from the schedule. Other modes: timetable by date, service alerts, live vehicles, stop and route dictionaries.

- **URL**: https://apify.com/yadroo/mbta-arrivals.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Travel, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 transit row returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## MBTA Boston: Bus & Train Arrivals, Timetables, Alerts

**When does the next bus or train leave this stop, and is the line disrupted right now?** This actor answers both as
plain rows from Boston's official transit API (`api-v3.mbta.com`): minutes away, headsign, direction, live-or-scheduled
flag, delay against the timetable, vehicle, stop and route ids. Every slot the agency does not predict — late night, a
trip two hours out, a stop without realtime coverage — is filled from the published timetable, so a run at 03:00 still
writes rows instead of an empty dataset. Six modes share one input: arrivals, timetable, alerts, vehicles, stops and
routes. No API key, no proxy, no browser.

### Use cases

- **Stop or platform display**: poll `arrivals` for one station and one direction every minute and show `minutesAway`,
  `headsign` and the live flag on a screen in a lobby, café or office.
- **Station departure board for one mode**: South Station carries commuter rail, subway and buses — filter
  *Modes of transport* to `commuter-rail` and you get the board a rail passenger wants, with `serviceDate` and track
  direction.
- **"What leaves near me" for an app or an agent**: give a latitude and longitude, get every stop within walking
  distance with a real `distanceMeters` and the next departures from each.
- **Service-status dashboard**: `alerts` writes every disruption in effect with effect, cause, severity, the routes and
  stops affected and the active window; add *Changed in the last N hours* plus *Only rows not seen before* for a daily
  watch that never reports the same alert twice.
- **Accessibility and step-free routing**: filter alerts to `ELEVATOR_CLOSURE`, `ESCALATOR_CLOSURE`, `ACCESS_ISSUE` and
  `STATION_ISSUE` to see which lifts are out before sending someone to a station.
- **Timetable export and analysis**: `schedule` writes every scheduled trip at a stop for one service date — trip id,
  stop sequence, local and UTC times, pick-up and drop-off flags — ready for a spreadsheet or a punctuality study.
- **Building the ids first**: `stops` and `routes` are the two dictionaries; run them once, keep the ids, and feed them
  into every other mode.

### Input

Every field is optional. Values shown in the Console form as examples are **prefills**, not defaults — only behaviour
settings have defaults, so no run is silently narrowed to a place you did not ask for.

| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
| `mode` | string | `arrivals` | `arrivals`, `schedule`, `alerts`, `vehicles`, `stops`, `routes` — see [Modes](#modes) |
| `stopQuery` | string | — (prefill `Park Street`) | A stop or station name as riders say it. Resolved against the source's own stop list, stations first; the id picked is written to the log and to `SUMMARY` |
| `stopIds` | string\[] | — | Source ids instead of a name: `place-pktrm`, `place-sstat`, `70075`, `1936`. A station id covers all its platforms |
| `routeIds` | string\[] | — | `Red`, `Orange`, `Blue`, `Mattapan`, `Green-B`…`Green-E`, bus numbers (`1`, `66`, `111`, `SL1`), commuter rail (`CR-Fitchburg`…). See [Route ids](#route-ids) |
| `routeTypes` | string\[] | — (all) | `light-rail`, `subway`, `commuter-rail`, `bus`, `ferry` |
| `latitude` | number | — | −90…90. Give it together with `longitude` instead of naming a stop |
| `longitude` | number | — | −180…180 (Boston is about −71) |
| `radiusMeters` | integer | `600` | 50–5000. Real metres: the actor converts to the degrees the source wants, then measures each stop itself |
| `directionId` | string | `any` | `any`, `0`, `1`. Each route names its own two directions — mode `routes` lists them |
| `minutesAhead` | integer | `120` | 5–1440. Mode `arrivals` only: how far ahead departures may lie |
| `serviceDate` | string | — (today in Boston) | `YYYY-MM-DD`. Used by `schedule`, and by `arrivals` for another day |
| `startTime` | string | — (whole day) | `HH:MM` Boston local, mode `schedule`. Past midnight counts on: `25:00` is 1 a.m. |
| `includeScheduled` | boolean | `true` | Mode `arrivals`: fill every slot without a prediction from the timetable and compute `delaySeconds` where both exist |
| `alertEffects` | string\[] | — (all) | See [Alert effects](#alert-effects) |
| `alertSeverityMin` | integer | — (all) | 0–10; 3 drops most information notices |
| `alertLifecycles` | string\[] | — (all) | `NEW`, `ONGOING`, `ONGOING_UPCOMING`, `UPCOMING` |
| `activeOnly` | boolean | `true` | Only alerts whose active period covers this moment |
| `sinceHours` | integer | — (no filter) | 1–720. Keep only alerts updated inside this many hours, counted in UTC |
| `onlyNew` | boolean | `false` | Write only rows whose key was not seen in an earlier run of the same filters |
| `maxItems` | integer | `50` | 1–2000 |
| `fields` | string\[] | — (all) | Keep only these fields, in this order |
| `apiKey` | string (secret) | — | Optional. Your own free key from the agency raises the request limit from 20 to 1000 per minute; sent as a header, never logged, never written to a row |

Typos in the dictionaries are corrected and reported (`"commuter rail"` → `commuter-rail`, `"Ornage"` → `Orange`,
`"timetable"` → `schedule`). A value nothing matches ends the run with a message naming the field — the search is
never widened behind your back.

### Reference

#### Modes

| `mode` | What one row is | Needs | Typical fields |
|---|---|---|---|
| `arrivals` | The next departure of one trip from one stop | a stop, a coordinate or a route | `minutesAway`, `timeLocal`, `headsign`, `predicted`, `delaySeconds` |
| `schedule` | One scheduled trip at one stop on one service date | a stop, a coordinate or a route | `serviceDate`, `timeLocal`, `tripId`, `stopSequence`, `pickUp` |
| `alerts` | One published disruption | nothing | `header`, `effect`, `severity`, `routeIdsAffected`, `activePeriodStart` |
| `vehicles` | One vehicle in service with its position | nothing (filter by route) | `latitude`, `longitude`, `currentStatus`, `bearing` |
| `stops` | One stop or station | nothing (filter by route or coordinate) | `stopId`, `stopName`, `municipality`, `wheelchairBoarding` |
| `routes` | One route or line | nothing | `routeId`, `routeName`, `directionNames`, `routeColor` |

`arrivals` works like this: the live predictions come first; if fewer rows than you asked for are predicted and
*Fill gaps from the timetable* is on, the published timetable for the same stops and window is merged in, marked
`source: "schedule"`; where a prediction and a timetable entry describe the same trip at the same stop, the row keeps
the live time and gains `scheduledTime` and `delaySeconds`. If the window holds nothing at all — after the last train
of the night — the run writes the first departures of the **next** service day rather than nothing.

#### Modes of transport

| `routeTypes` value | `routeType` | Covers |
|---|---|---|
| `light-rail` | 0 | Green Line branches, Mattapan trolley |
| `subway` | 1 | Red, Orange and Blue lines |
| `commuter-rail` | 2 | All `CR-…` lines |
| `bus` | 3 | Numbered bus routes and the Silver Line (`SL1`…) |
| `ferry` | 4 | Harbor ferry routes (`Boat-…`) |

#### Route ids

Rapid transit uses colour names: `Red`, `Orange`, `Blue`, `Mattapan`, `Green-B`, `Green-C`, `Green-D`, `Green-E`.
Buses use their number as the id (`1`, `39`, `66`, `111`) and the Silver Line uses `SL1`…`SL5`. Commuter rail lines are
`CR-` plus the line name: `CR-Fitchburg`, `CR-Providence`, `CR-Newburyport`, `CR-Worcester`, `CR-Fairmount`, and so on
(13 lines at the time of writing). Ferries start with `Boat-`. Run mode `routes` for the current list with names,
colours and direction names; give `routeTypes` to get one mode's lines only.

#### Stop ids

Station ids start with `place-` (`place-pktrm` = Park Street, `place-sstat` = South Station, `place-north` = North
Station, `place-portr` = Porter). Subway platforms are four-digit numbers (`70075`), bus poles are shorter numbers
(`1936`), commuter rail platforms look like `FR-0034-02`. A station id in `stopIds` covers every platform under it, and
the rows then carry both the platform (`stopId`) and the station (`parentStationId`). Run mode `stops` with a route or
a coordinate to get the ids for your area.

#### Alert effects

`DELAY`, `SHUTTLE`, `DETOUR`, `SUSPENSION`, `CANCELLATION`, `STATION_CLOSURE`, `STOP_CLOSURE`, `STOP_MOVE`,
`TRACK_CHANGE`, `SCHEDULE_CHANGE`, `SERVICE_CHANGE`, `STATION_ISSUE`, `ACCESS_ISSUE`, `ELEVATOR_CLOSURE`,
`ESCALATOR_CLOSURE`, `SNOW_ROUTE`, `NO_SERVICE`, `POLICY_CHANGE`, `NOTICE`, `SUMMARY`.

Causes seen in the live feed include `UNKNOWN_CAUSE`, `CONSTRUCTION`, `TRAFFIC`, `MAINTENANCE`, `WEATHER`,
`SEVERE_WEATHER`, `ACCIDENT`, `POLICE_ACTIVITY`, `MEDICAL_EMERGENCY`. Lifecycles are `NEW`, `ONGOING`,
`ONGOING_UPCOMING` and `UPCOMING`. Severity runs 0–10: 1 is an information notice, 3–5 a real disruption, 7 and above a
line-wide event. Effects are filtered on the rows, so an effect nobody published today simply yields fewer rows.

#### Vehicle status values

`INCOMING_AT`, `STOPPED_AT`, `IN_TRANSIT_TO`; occupancy, when the agency reports it, is one of
`MANY_SEATS_AVAILABLE`, `FEW_SEATS_AVAILABLE`, `STANDING_ROOM_ONLY`, `CRUSHED_STANDING_ROOM_ONLY`, `FULL`,
`NO_DATA_AVAILABLE`.

### Examples

**Next Red Line trains at Park Street (a stop display)**

```json
{ "mode": "arrivals", "stopQuery": "Park Street", "routeIds": ["Red"], "maxItems": 15 }
```

**Commuter rail departures from South Station (one mode at a multi-mode station)**

```json
{ "mode": "arrivals", "stopQuery": "South Station", "routeTypes": ["commuter-rail"], "maxItems": 20 }
```

**Buses near a coordinate in downtown Boston**

```json
{ "mode": "arrivals", "latitude": 42.3601, "longitude": -71.0589, "radiusMeters": 500, "routeTypes": ["bus"], "maxItems": 20 }
```

**Every disruption in effect right now**

```json
{ "mode": "alerts", "maxItems": 30 }
```

**Lift and escalator outages for step-free routing**

```json
{ "mode": "alerts", "alertEffects": ["ELEVATOR_CLOSURE", "ESCALATOR_CLOSURE", "ACCESS_ISSUE", "STATION_ISSUE"], "maxItems": 20 }
```

**Today's full commuter rail timetable at Porter**

```json
{ "mode": "schedule", "stopQuery": "Porter", "routeIds": ["CR-Fitchburg"], "maxItems": 40 }
```

**The two dictionaries: Orange Line stops, then the commuter rail lines**

```json
{ "mode": "stops", "routeIds": ["Orange"], "maxItems": 30 }
```

```json
{ "mode": "routes", "routeTypes": ["commuter-rail"], "maxItems": 25 }
```

### Output

One real row from the cloud run `Cu0CI8DmcadgyYTFB` (input: the first example above), 27 September 2026, 18:20 Boston:

```json
{
  "rowType": "arrival",
  "found": true,
  "stopId": "70076",
  "stopName": "Park Street",
  "parentStationId": "place-pktrm",
  "parentStationName": "Park Street",
  "municipality": "Boston",
  "distanceMeters": null,
  "latitude": 42.3563946,
  "longitude": -71.0624242,
  "routeId": "Red",
  "routeName": "Red Line",
  "routeShortName": null,
  "routeType": 1,
  "routeTypeName": "subway",
  "routeColor": "DA291C",
  "directionId": 1,
  "directionName": "North",
  "headsign": "Alewife",
  "tripId": "77916490",
  "tripName": null,
  "vehicleId": "R-548BD870",
  "vehicleLabel": "1920",
  "arrivalTime": "2026-09-27T22:19:27.000Z",
  "departureTime": "2026-09-27T22:20:34.000Z",
  "time": "2026-09-27T22:20:34.000Z",
  "timeLocal": "2026-09-27T18:20:34-04:00",
  "scheduledTime": "2026-09-27T22:15:00.000Z",
  "delaySeconds": 334,
  "serviceDate": "2026-09-27",
  "minutesAway": 10,
  "predicted": true,
  "source": "prediction",
  "status": null,
  "scheduleRelationship": null,
  "stopSequence": 150,
  "lastTrip": false,
  "url": "https://www.mbta.com/stops/place-pktrm",
  "fetchedAt": "2026-09-27T22:10:11.262Z"
}
```

An alert row from the same suite (run `yM0xAAggcS8hPg1Dn`), shortened:

```json
{
  "rowType": "alert",
  "alertId": "1035169",
  "header": "Route 44 is experiencing delays of about 20 minutes due to traffic.",
  "effect": "DELAY",
  "cause": "TRAFFIC",
  "severity": 5,
  "lifecycle": "NEW",
  "serviceEffect": "Route 44 delay",
  "active": true,
  "activePeriodStart": "2026-09-27T21:59:28.000Z",
  "activePeriodEnd": "2026-09-28T00:10:00.000Z",
  "updatedAt": "2026-09-27T21:59:28.000Z",
  "routeIdsAffected": [
    "44"
  ],
  "stopIdsAffected": [],
  "routeTypesAffected": [
    "bus"
  ],
  "activities": [
    "BOARD",
    "EXIT",
    "RIDE"
  ],
  "url": "https://www.mbta.com/schedules/44/alerts",
  "fetchedAt": "2026-09-27T22:10:15.343Z"
}
```

#### Fields

| Field | Type | Meaning |
|---|---|---|
| `rowType` | string | `arrival`, `schedule`, `alert`, `vehicle`, `stop`, `route` or `notFound` |
| `found` | boolean | `false` only on the single row a run writes when nothing matched |
| `stopId`, `stopName` | string | The platform or stop the row happens at, and its name |
| `parentStationId`, `parentStationName` | string | The station the platform belongs to |
| `municipality` | string | Town the stop is in |
| `distanceMeters` | number | Distance from your coordinate, measured by the actor (coordinate searches only) |
| `latitude`, `longitude` | number | Stop position — for a `vehicle` row, the vehicle's own position |
| `routeId`, `routeName`, `routeShortName` | string | The route: id used by the filters, long name, number |
| `routeType`, `routeTypeName` | number, string | 0–4 and its name (`subway`, `bus`, …) |
| `routeColor` | string | The line's colour as a hex triplet, for a map or a badge |
| `directionId`, `directionName` | number, string | 0 or 1 and the name that route gives it (`South`, `Inbound`, `Alewife`) |
| `headsign` | string | What the vehicle shows — the destination of this trip |
| `tripId`, `tripName` | string | Trip identity; commuter rail also numbers its trains |
| `vehicleId`, `vehicleLabel` | string | The vehicle serving the trip, when the agency tracks it |
| `arrivalTime`, `departureTime` | string | ISO 8601 **UTC**; either may be empty at the first or last stop of a trip |
| `time` | string | The time the row is sorted and counted by: departure if there is one, else arrival (UTC) |
| `timeLocal` | string | The same moment with Boston's own offset, exactly as the agency publishes it |
| `scheduledTime` | string | The timetable time for this trip and stop (UTC), when it exists |
| `delaySeconds` | number | `time` − `scheduledTime`; positive = late, negative = early |
| `serviceDate` | string | The service day the trip is counted under — a 00:40 train belongs to the previous date |
| `minutesAway` | number | Whole minutes from the start of the run; empty for a timetable row already in the past |
| `predicted` | boolean | `true` = live prediction, `false` = published timetable |
| `source` | string | `prediction` or `schedule` |
| `status`, `scheduleRelationship` | string | The agency's own words about the trip (`ADDED`, `CANCELLED`, …) when it says anything |
| `stopSequence` | number | Position of this stop in the trip |
| `pickUp`, `dropOff`, `timepoint` | boolean | Timetable rows: whether the trip boards, alights and keeps time here |
| `lastTrip` | boolean | Prediction rows: the last trip of the night on that route |
| `alertId`, `header`, `shortHeader`, `description` | string | Alert identity and the rider-facing text |
| `effect`, `cause`, `severity`, `lifecycle`, `serviceEffect`, `timeframe`, `banner` | — | The alert classified: see [Alert effects](#alert-effects) |
| `active`, `activePeriodStart`, `activePeriodEnd` | boolean, string | Whether it is in effect now and the window (UTC) |
| `createdAt`, `updatedAt` | string | When the agency published and last touched it (UTC) |
| `routeIdsAffected`, `stopIdsAffected`, `routeTypesAffected`, `activities` | array | What the alert names as affected |
| `currentStatus`, `currentStopSequence`, `bearing`, `speed`, `occupancyStatus`, `revenue` | — | Vehicle rows: what it is doing and where it points |
| `locationType`, `locationTypeName`, `platformName`, `platformCode`, `address`, `description` | — | Stop rows: what kind of place it is |
| `wheelchairBoarding` | number | 0 unknown, 1 accessible, 2 not accessible |
| `matchedRouteIds` | array | Stop rows: which of your route filters this stop serves |
| `directionNames`, `directionDestinations`, `routeTextColor`, `routeDescription`, `fareClass`, `sortOrder` | — | Route rows |
| `url` | string | The agency page for the station, line or alert — for a human to open |
| `fetchedAt` | string | When the run read the source, ISO 8601 UTC |

All timestamps are UTC (`…Z`) except `timeLocal`, which keeps Boston's offset on purpose. Dataset views:
**Next arrivals**, **Service alerts**, **Live vehicles** and **Stops & routes**. A run also writes a `SUMMARY` record to
the key-value store with the resolved stop, the filters, how many rows were live and how many came from the timetable,
the request count and any warnings.

If nothing matches — a name no stop carries, an alert filter nothing satisfies — the dataset gets exactly one row with
`found: false` and a `message` saying why, never a silent empty success.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~mbta-arrivals/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"arrivals","stopQuery":"Park Street","routeIds":["Red"],"maxItems":15}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/mbta-arrivals').call({ mode: 'arrivals', stopQuery: 'South Station', routeTypes: ['commuter-rail'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/mbta-arrivals").call(run_input={"mode": "alerts", "alertSeverityMin": 3, "maxItems": 30})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

MCP: add `https://mcp.apify.com` to Claude / Cursor / any MCP client and call the `yadroo/mbta-arrivals` tool with the
same JSON input.

A display should poll a narrow window (`minutesAhead` 30, `maxItems` 10) rather than the whole day — it is faster and
cheaper. Trim the row to what you render with `fields`, e.g.
`["stopName", "routeName", "headsign", "minutesAway", "predicted"]`. For a watch, schedule the run and set `onlyNew`:
the keys are remembered per filter combination, so the same alert is never paid for twice.

### Pricing

Pay per event: **$0.001 per run start plus $0.002 per dataset row** on the free tier. The row price falls with your
Apify plan — Bronze $0.0018, Silver $0.0016, Gold and above $0.0014 — while the start event stays $0.001. Every run is
charged the start event, including runs that write a single `found: false` row.

| Run | Rows | Free tier | Gold and above |
|---|---|---|---|
| Stop display, one direction | 10 | $0.001 + $0.020 = **$0.021** | $0.001 + $0.014 = **$0.015** |
| Station board (the default `maxItems`) | 50 | $0.001 + $0.100 = **$0.101** | $0.001 + $0.070 = **$0.071** |
| Timetable export for a stop | 300 | $0.001 + $0.600 = **$0.601** | $0.001 + $0.420 = **$0.421** |

A typical run takes a few seconds at 256 MB, so platform compute is a fraction of a cent on top.

### Limits & FAQ

- **Rate limit.** Anonymous callers get 20 requests a minute from the source, and the actor paces itself under that;
  a run that has to page the whole stop list can therefore take a minute. Paste your own free key from the agency into
  *Your own API key* and the limit becomes 1000 a minute. The key is sent as a header, never logged and never written
  to a row.
- **Predictions exist only where the agency predicts.** Realtime covers trips that are being tracked; a stop with no
  vehicle on the way, or the middle of the night, has none. That is exactly why the timetable fallback exists and why
  most rows outside rush hour say `source: "schedule"`.
- **Vehicle positions are empty overnight.** Roughly 02:00–04:30 Boston time nothing is moving, so mode `vehicles`
  legitimately returns the one `found: false` row. Use `arrivals` if you need something to show at that hour.
- **The published timetable covers roughly the current quarter.** A `serviceDate` far in the past or future has no
  trips, and the run says so rather than returning an empty dataset.
- **Alert filters can legitimately return nothing.** A quiet hour with `alertSeverityMin: 7` is a network running well.
- **Ambiguous names.** "Harvard" is a station and also several bus stops; the actor picks the station and writes the id
  it used into the log and `SUMMARY`. Use `stopIds` when you need to be certain.
- **Service days, not calendar days.** A trip at 00:40 belongs to the previous service date and the timetable counts it
  as 24:40. `serviceDate` always shows the agency's own day.
- **What is not here.** No fare or trip-planning data, no historical performance archive, no vehicle occupancy where the
  agency does not publish it, and no pages from the agency's website — the actor reads only the public JSON API.
- **Source and attribution.** Data comes from the MBTA's public V3 API and is governed by the MassDOT Developers
  License Agreement; attribute the agency when you republish it, and do not present the output as an official service.
  Alert text is the agency's wording and is passed through unchanged.

***

Made by **Yadroo**. Sibling actors: [open-meteo-weather](https://apify.com/yadroo/open-meteo-weather),
[osm-geocode](https://apify.com/yadroo/osm-geocode), [nasa-eonet-events](https://apify.com/yadroo/nasa-eonet-events),
[public-holidays](https://apify.com/yadroo/public-holidays), [rss-to-json](https://apify.com/yadroo/rss-to-json).

# Actor input Schema

## `mode` (type: `string`):

`arrivals` answers 'when does the next one leave here': predictions first, and for every slot the source does not predict (late night, a trip hours ahead, a stop without realtime) the published timetable is used, so a run at 03:00 still writes rows. `schedule` ignores realtime and writes the whole timetable of a service date. `alerts` writes the disruptions the agency publishes. `vehicles` writes one row per vehicle in service with its position. `stops` and `routes` are the two dictionaries: run them once to learn the ids the filters below accept.

## `stopQuery` (type: `string`):

A station or stop name as riders say it: `Park Street`, `South Station`, `Porter`, `Harvard`, `Nubian`. The actor resolves it against the source's own stop list — stations first, then platforms and bus stops — and reports the id it picked in the run log. Ambiguous names resolve to the busiest match; write the exact name or use *Stop IDs* to be precise. A name nothing matches yields one row with `found: false`, never a silent empty run.

## `stopIds` (type: `array`):

Source ids instead of a name, several allowed: `place-pktrm` (Park Street), `place-sstat` (South Station), `70075` (one subway platform), `1936` (a bus stop pole). A station id covers every platform under it. Run mode `stops` to get the ids for your area or line.

## `routeIds` (type: `array`):

Keep only these routes: `Red`, `Orange`, `Blue`, `Mattapan`, `Green-B`…`Green-E` for rapid transit, a bus number such as `1`, `66`, `111`, `SL1`, or a commuter rail line such as `CR-Fitchburg`, `CR-Providence`, `CR-Newburyport`. Without a stop filter this returns arrivals, alerts or vehicles for the whole route. Mode `routes` writes the full list of ids.

## `routeTypes` (type: `array`):

Keep only these modes. Useful at a stop several modes share: South Station has commuter rail, subway and buses, so `commuter-rail` alone gives a departure board a station screen can show. Empty = every mode.

## `latitude` (type: `number`):

Search stops around a point instead of naming one. Give latitude and longitude together, e.g. 42.3601 / -71.0589 for downtown Boston. Every row then carries `distanceMeters`, the real distance from your point, and rows are written nearest first.

## `longitude` (type: `number`):

The other half of the coordinate. Boston longitudes are negative (about -71).

## `radiusMeters` (type: `integer`):

How far around the coordinate to look, in metres. 300 is one block, 600 a short walk, 1500 a long one. The source's own radius filter works in degrees, so the actor converts, asks for slightly more and then measures each stop, which is why `distanceMeters` never exceeds what you asked for.

## `directionId` (type: `string`):

Every route names its two directions itself; the row shows the name (`Inbound`, `Southbound`, `Alewife`) next to the number. A platform display normally wants one direction only. Mode `routes` lists the direction names and destinations per route.

## `minutesAhead` (type: `integer`):

Mode `arrivals` only: how far into the future departures may lie. 30 for a stop display, 120 for a planning board, 720 for the rest of the day. If nothing at all runs inside the window — after the last trip of the night — the actor falls back to the first departures of the next service day rather than returning nothing, and those rows are marked `source: schedule`.

## `serviceDate` (type: `string`):

YYYY-MM-DD, used by mode `schedule` (and by `arrivals` when you want another day). Empty = today in Boston local time. A service date runs past midnight: trips after 00:00 belong to the day before, and the actor writes the date the agency counts them under. The published feed covers roughly the current quarter, so a date far in the past or future has no trips.

## `startTime` (type: `string`):

HH:MM in Boston local time, mode `schedule` only. Empty = the whole service day from its first trip. Values past midnight are written as 24:xx or 25:xx, the way the timetable counts them, so `25:00` means 1 a.m. of the next calendar day.

## `includeScheduled` (type: `boolean`):

On (the default) mode `arrivals` adds scheduled departures for every slot without a live prediction, marks them `source: schedule` and, where both exist for the same trip, writes `delaySeconds` — the prediction minus the timetable. Off, only what the source predicts right now is written, which is closer to a raw realtime feed and can be empty outside service hours.

## `alertEffects` (type: `array`):

Keep only alerts of these kinds. The accessibility set (`ELEVATOR_CLOSURE`, `ESCALATOR_CLOSURE`, `ACCESS_ISSUE`, `STATION_ISSUE`) is what a step-free routing tool needs; `DELAY`, `SHUTTLE`, `SUSPENSION` and `DETOUR` are what a status board shows. Filtering happens on the `effect` field of the rows, so an effect nobody published today simply yields fewer rows. Empty = every kind.

## `alertSeverityMin` (type: `integer`):

The source grades each alert from 0 to 10; in practice 1 is an information notice, 3–5 a real disruption, 7 and above a line-wide event. Set 3 to drop the elevator and signage notices from a status board. Empty = keep all.

## `alertLifecycles` (type: `array`):

Where the alert stands in its life. `NEW` plus `UPCOMING` is the feed for 'what has been announced since yesterday'; `ONGOING` is what riders are living with now. Empty = every lifecycle.

## `activeOnly` (type: `boolean`):

On (the default) only alerts whose active period covers this moment are written and `active` is true. Off, planned and past ones come too, each with its `activePeriodStart` and `activePeriodEnd`, which is how you export next weekend's shuttle plan.

## `sinceHours` (type: `integer`):

Keep only alerts the source updated inside this many hours, counted in UTC from the start of the run. 24 on a daily task is the honest 'what is new since yesterday' feed. Empty = no time filter.

## `onlyNew` (type: `boolean`):

Remember each row's key in this actor's key-value store (alert id and update time, or trip plus stop plus scheduled time) and write only keys that were not there on the previous run. The first run writes everything it finds, later runs write what appeared since — a watch that does not pay for the same alert twice. Leave it off for a departure board, where you want the full picture each run.

## `maxItems` (type: `integer`):

Stop after this many rows. A stop display needs 5–20, a station board 50, a whole-line timetable export a few hundred. The source is polite but rate-limited for anonymous callers, so large exports are paced and take longer.

## `fields` (type: `array`):

Keep only these fields, in this order, e.g. \["stopName", "routeName", "headsign", "minutesAway"]. Empty = every field the mode fills.

## `apiKey` (type: `string`):

Not required: the actor reads the source anonymously. The agency also hands out free keys that raise the request limit from 20 to 1000 per minute; paste one here and big exports run faster. It is sent as a header, never logged and never written to the dataset.

## Actor input object example

```json
{
  "mode": "arrivals",
  "stopQuery": "Park Street",
  "radiusMeters": 600,
  "directionId": "any",
  "minutesAhead": 120,
  "includeScheduled": true,
  "activeOnly": true,
  "onlyNew": false,
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "stopQuery": "Park Street"
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/mbta-arrivals").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "stopQuery": "Park Street" }

# Run the Actor and wait for it to finish
run = client.actor("yadroo/mbta-arrivals").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "stopQuery": "Park Street"
}' |
apify call yadroo/mbta-arrivals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yadroo/mbta-arrivals"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/duuRCrPMmvSCfmJCz/builds/HWqLUBywlKzOwxlgw/openapi.json
