How to Set Up OneBusAway Maglev with Bay Area Transit Data
Maglev is a Apache 2.0 licensed Go implementation of the OneBusAway REST API. It reads GTFS static feeds and GTFS-RT feeds, serves the OBA API on top of them, and runs comfortably on a small VPS. The most common question I get about it is: how do I actually point it at real data?
This post walks you through setting up Maglev with data from the San Francisco Bay Area. 511.org publishes a consolidated regional feed that combines every transit operator in the Bay Area into one set of endpoints. BART, Muni, Caltrain, AC Transit, Golden Gate, VTA, SamTrans — all of it, behind a single GTFS feed and three GTFS-RT feeds. It’s the best showcase I know of for what Maglev can do, because it exercises every code path at once: large static data, high real-time update volume, and a dozen agencies coexisting in one database.
Get a 511 API key
Before anything else, go to 511.org/open-data/transit and request an API key. It’s free and the form is short. You’ll get a key by email, usually within a few minutes.
Make a note of the key. You’ll use it in four URLs.
Clone and build Maglev
git clone https://github.com/OneBusAway/maglev.git
cd maglev
make build
Maglev needs Go 1.24.2 or later. If make build fails with a toolchain error, update Go and try again.
This produces a binary at bin/maglev. Don’t run it yet — it needs a config file.
Write the config
Copy the example config, then overwrite it:
cp config.example.json config.json
Replace the contents of config.json with this, substituting your 511 key everywhere you see YOUR_API_KEY:
{
"$schema": "./config.schema.json",
"port": 4000,
"env": "development",
"api-keys": ["test"],
"rate-limit": 100,
"log-level": "info",
"gtfs-static-feed": {
"url": "http://api.511.org/transit/datafeeds?operator_id=RG&api_key=YOUR_API_KEY"
},
"gtfs-rt-feeds": [
{
"id": "511-regional",
"vehicle-positions-url": "https://api.511.org/Transit/VehiclePositions?agency=RG&api_key=YOUR_API_KEY",
"trip-updates-url": "https://api.511.org/Transit/TripUpdates?agency=RG&api_key=YOUR_API_KEY",
"service-alerts-url": "https://api.511.org/Transit/servicealerts?agency=RG&api_key=YOUR_API_KEY",
"refresh-interval": 30
}
],
"data-path": "./gtfs.db"
}
A few things worth noticing:
operator_id=RGis the regional feed. Every other operator ID returns data for a single agency;RGreturns the union of all of them. The GTFS-RT endpoints useagency=RGfor the same reason.- I’m not setting
agency-idson the RT feed. When you omit it, Maglev accepts updates for every agency in the static feed, which is what you want with a regional feed. refresh-intervalis in seconds. 30 is sensible here. Going lower won’t get you fresher data — 511 updates on its own cadence — and you’ll just burn through your quota.- The
api-keyslist is for your Maglev clients, not 511. Change"test"to something harder to guess before you put this on the public internet.
Run it
./bin/maglev -f config.json
First boot takes a minute or two. Maglev downloads the regional GTFS zip (around 80 MB), unpacks it, and writes a SQLite database to ./gtfs.db. You’ll watch log lines scroll as it loads agencies, routes, stops, trips, and stop times. Once it starts the HTTP server, it begins fetching the three GTFS-RT feeds every 30 seconds.
In another terminal, verify it’s alive:
curl http://localhost:4000/healthz
You should get OK.
Now hit the OBA API. Every endpoint requires a key from your api-keys list:
curl 'http://localhost:4000/api/where/agencies-with-coverage.json?key=test' | jq .
You’ll see every Bay Area operator in the regional feed, each with its coverage bounding box. Pick an agency ID, such as SF for SFMTA Muni from that response and pull its routes. Note that Maglev is case sensitive about agency IDs: SF works but sf fails.
curl 'http://localhost:4000/api/where/routes-for-agency/SF.json?key=test' | jq '.data.list | length'
Finally, grab real-time vehicle positions:
curl 'http://localhost:4000/api/where/vehicles-for-agency/SF.json?key=test' | jq '.data.list | length'
If that returns a number greater than zero, your whole pipeline is working end-to-end: Maglev fetched the protobuf feed from 511, merged it with the static data, and is serving it back through the OneBusAway API.
Exploring more API endpoints
Before we conclude, let’s explore a few other API endpoints provided by Maglev. You can find the complete list on our developer docs website. The Maglev server is designed to be 100% API compatible with the older, Java-based OBA REST API server.
First, retrieve the list of every stop name and ID for your selected agency ID:
curl 'http://localhost:4000/api/where/stops-for-agency/SF.json?key=test' | jq '.data.list[] | {name, id}'
Second, choose the ID for your desired transit stop, and feed it to the arrivals-and-departures-for-stop API:
curl 'http://localhost:4000/api/where/arrivals-and-departures-for-stop/SF_17783.json?key=test' | \
jq '.data.entry.arrivalsAndDepartures[] | {distanceFromStop, lastUpdateTime, numberOfStopsAway, predictedDepartureTime, routeLongName, routeShortName, scheduledDepartureTime, tripHeadsign, lat: .tripStatus.lastKnownLocation.lat, lon: .tripStatus.lastKnownLocation.lon, occupancyStatus: .tripStatus.occupancyStatus}'
This will give you a JSON output similar to the following:
{
"distanceFromStop": 3.488730084907729,
"lastUpdateTime": 1776646972000,
"numberOfStopsAway": -5,
"predictedDepartureTime": 0,
"routeLongName": "MASONIC",
"routeShortName": "43",
"scheduledDepartureTime": 1776646968000,
"tripHeadsign": "Fort Mason",
"lat": 37.800350189208984,
"lon": -122.45325469970703,
"occupancyStatus": "MANY_SEATS_AVAILABLE"
}
{
"distanceFromStop": 3.488730084907729,
"lastUpdateTime": 1776646972000,
"numberOfStopsAway": 12,
"predictedDepartureTime": 1776647942000,
"routeLongName": "MASONIC",
"routeShortName": "43",
"scheduledDepartureTime": 1776648108000,
"tripHeadsign": "Fort Mason",
"lat": 37.769805908203125,
"lon": -122.44844055175781,
"occupancyStatus": "MANY_SEATS_AVAILABLE"
}
What you actually have
You now have a complete OneBusAway backend for the Bay Area, running on your laptop, serving real data. You can point the OneBusAway iOS app at it, or the Android app, or the Wayfinder web UI, or anything else that speaks the OBA API. Everything our production servers do, this does too.
The reason I find this valuable isn’t that I personally need a Bay Area transit app — 511 already publishes its own consumer app. It’s that Maglev makes transit data hackable. Want to add an endpoint the official API doesn’t have? Modify a query. Curious how agencies differ in their GTFS-RT implementations? Run two instances pointed at different feeds and diff the responses. The moment spinning up a real working OBA deployment drops to a five-minute exercise, the cost of experimenting with transit data drops to near zero.
That’s the point. Spin it up, poke around, and when you’re done, rm gtfs.db.
Call to Action
Maglev is still very much a late alpha or early beta software package, and it’s under heavy development. We’re always looking for passionate urbanists and transit enthusiasts to volunteer on the project, and help us get it to production quality. If this is of interest to you, please either:
- Go to the GitHub repo for the project, pick an issue, and start hacking on it, or
- Reach out to us through OSSVolunteers. There are several volunteer roles we’re recruiting for, not just engineers!
And then join us on Slack, introduce yourself, and become a part of our community!