Best Practices
More from Developers
Select from the list below to learn more about the information and tools available for developers interested in MBTA data.
Documentation
The Swagger documentation is the best source for information about the different endpoints and fields we provide. Some particular places to look:
- The endpoint documentation includes what filters are available and what additional information can be included
- The resource documentation includes the format of the fields, as well as what the values can be
Rate limiting
Usage of the API v3 is subject to certain rate limits:
- Requests made without an API key are subject to a limit of 20 requests per minute.
- Requests made with a valid API key are limited to a default of 1000 requests per minute.
You can register to request an API key here. If you already have an API key, you can also view your current rate limit in the portal; you can also request an increase to your rate limit if you need one. See the sections of this document on Caching and Fields for ways to fetch less data and avoid hitting rate limits.
All API v3 responses include HTTP headers which show your rate limit status:
Header | Description | ||
---|---|---|---|
Header | x-ratelimit-limit | Description | The maximum number of requests you’re allowed to make per time window. |
Header | x-ratelimit-remaining | Description | The number of requests remaining in the current time window. |
Header | x-ratelimit-reset | Description | The time at which the current rate limit time window ends in UTC epoch seconds. |
Caching
API v3 supports caching via the `Last-Modified` response and `If-Modified-Since` request headers. Each response contains a `Last-Modified` header, specifying the last time that data was updated. If, on subsequent requests, your client passes an `If-Modified-Since` header with that value and the data hasn't changed, you'll quickly receive a 304 Not Modified. This cached response won't count against your API key limit either. Another advantage of using this header is that you won't receive an update if you hit a server which was updated slightly in the past.
Note: This only works for the root data type; included data isn't currently tracked by the `Last-Modified` header.
Compression
API v3 supports GZIP compression via the `Accept-Encoding` header. If your HTTP client doesn't do this transparently, you can pass `Accept-Encoding: gzip` as a request header, and the response will be compressed. This can result in large data savings: the full list of routes goes from 48k without compression to 3.4k with compression.
Sparse fieldsets
Each type of data supports a query parameter `fields[type]` which limits the returned attributes (reference). For example, https://api-v3.mbta.com/routes/?fields%5Broute%5D=short_name,long_name returns only the names for the routes. If you know what fields you need, this is another good way to reduce the amount of data you receive. This also works for included data types: https://api-v3.mbta.com/route_patterns?filter[route]=CR-Providence&include=representative_trip&fields[trip]=headsign .
Nested includes
An often-overlooked JSON:API feature is the ability to include
multiple levels of relationships, by connecting the relationship names with dots (reference). For example, the response for https://api-v3.mbta.com/routes/Red?include=route_patterns.representative_trip.shape will include the requested route, its route patterns, the representative trips of those patterns, and the shapes of those trips. This works to any "depth" of relationships, and can save you the need to make N+1 requests in many situations.
Updates
The realtime data can update very frequently, even using `If-Modified-Since` headers to avoid stale data. You may want to include some logic in your clients to prevent relative times from bouncing (say between "3 minutes away" and "4 minutes away") if that would be confusing to your users. If you're displaying predictions at that level of granularity, you can also reduce the frequency of updates accordingly.
Alerts
Displaying alerts is one of the trickiest features to get correct. Service disruptions can affect large sets of riders, and you as the client developer are in the best position to know where they might be trying to ride.
If you don’t know where along a route a rider is traveling, filtering only by their origin stop (see example) is likely to miss relevant alerts at other stops along the line. In that case, you should also query for alerts on the entire route (see example) and incorporate them.
The same recommendation applies to implementations where the filtering of the alerts to show happens on the client side. You want to add the alerts where any informed entity includes the relevant route. Even if an alert is about a specific stop, it still may affect your riders.
When you do have the information about the rider's entire trip (where they are leaving from and where they are going), you still should consider the alerts on the stops along their route. For example, there might be an alert about shuttles replacing regular service somewhere in the middle of their trip. You can query for multiple stops by comma-separating them in the filter (see example).
You can also use the activity field of the Informed Entity to better filter to those alerts.
- For stops where the rider boards a vehicle (even transfers), filter for the BOARD activity
- For stops where the rider gets off a vehicle (even transfers), filter for the EXIT activity
- For stops the rider travels through, filter for the RIDE activity
- If the user requests accessible trips, filter for the USING_WHEELCHAIR activity
The documentation for AlertResource has more detail on other types of activities.
Alerts with a severity of 1 are informational. If possible, they can receive a more muted visual treatment compared with more severe alerts.
Predictions
Displaying real-time predictions is a popular use of the API, and there are some complexities involved in interpreting and displaying this data correctly.
Predictions may have an associated `schedule`, which represents the stop as it was originally scheduled to occur. The prediction can be thought of as a real-time "update" to the schedule; the `schedule_relationship` attribute indicates the nature of the update. Its possible values, and their meanings, are listed in the PredictionResource section of the Swagger API docs.
Note that predictions may not have a `schedule`, indicating we know a vehicle is running on the route but cannot match it to a scheduled trip.
In general, schedules should be ignored when displaying real-time predictions for rapid transit routes (`route_type` 0 and 1). Schedules are useful for time periods beyond when we have real-time predictions available.
Predictions, as well as schedules, may include an `arrival_time`, a `departure_time`, both, or (only for predictions) neither. The rules for when a time is present are simple:
- The departure time is present if, and only if, it's possible for riders to board the associated `vehicle` at the associated `stop`. A null departure time is typically seen at the last stop on a trip.
- The arrival time is present if, and only if, it's possible for riders to alight from the associated `vehicle` at the associated `stop`. A null arrival time is typically seen at the first stop on a trip.
- Commuter Rail predictions with neither a departure time nor arrival time, often have a status field with their boarding status. Depending on your use case, these predictions may be discarded, or the status may be displayed verbatim to the user (see "Status field" section below).
- Predictions with none of an arrival time, departure time, nor status, indicate the vehicle will not make the scheduled stop. The `schedule_relationship` field may explain why.
In general we recommend not displaying predictions with null departure times, since riders will not be able to board the vehicle. If both arrival and departure time are present, the arrival time is likely to be more useful to riders.
Predictions may have a free-text `status`. If present, we recommend displaying this directly to riders wherever you display an arrival or departure time, and if possible in place of the arrival or departure time, since it may indicate a situation that affects the accuracy of the prediction (e.g. a train stopped unexpectedly between stations). Status strings are English-language and have no maximum length, though they are typically kept short for display on signage.
The most popular way to display prediction data for a given stop is a "countdown" of how many minutes away the next few vehicles are, with destinations (e.g. "Ashmont" or "Braintree" for the Red Line). These are the rules we follow on the MBTA website and station signs.
The destination component is the `headsign` attribute of the `trip` associated with the `vehicle` of the prediction. The countdown component is determined by following these rules in order, stopping at the first applicable “display” or “do not display” instruction:
- If `status` is non-null:
- Display this value as-is
- If `departure_time` is null:
- Do not display this prediction, since riders won’t be able to board the vehicle
- Calculate the number of seconds until the vehicle reaches the stop, by subtracting the current time from the arrival time (if available) or the departure time (if not); call this value "seconds"
- If seconds < 0
- Do not display this prediction, since the vehicle has already left the stop
- If seconds <= 90, and the `status` of the associated `vehicle` is "STOPPED_AT", and the vehicle’s `stop` is the same as the prediction’s `stop`:
- Display "Boarding" (abbrev. "BRD")
- If seconds is <= 30
- Display "Arriving" (abbrev. "ARR")
- If seconds is <= 60
- Display "Approaching" (abbrev. "1 min")
- Round the seconds value to the nearest whole number of minutes, rounding up if exactly in-between; call this value "minutes"
- If minutes > 20
- Display “20+ minutes” (abbrev. “20+ min”)
- Display the number of minutes followed by "minutes" (abbrev. "min"). For example:
- Up to 89 seconds: "1 minute" or "1 min"
- 90 to 149 seconds: "2 minutes" or "2 min"
- 150 to 209 seconds: "3 minutes" or "3 min"