Developer documentation

The transit API.

This is the live data feed behind the academy’s Live Transit map and the bus count on the Live Transit page: Detroit Department of Transportation (DDOT) bus positions, QLINE streetcar positions, and DDOT service alerts, published as static JSON files on this site. A GitHub Actions workflow collects the agency feeds — the keys never leave Actions — and the academy’s publisher turns the collected feeds into the finished files on this site. Positions poll continuously and publish about every five minutes; the map dead-reckons between updates. No key required.

Base URL: https://detroitautomationacademy.com

Endpoints

GET /live-data/vehicles.json
Parameters
None.
Refresh
The publisher runs about every 5 minutes and republishes the file when the underlying data has changed (and at least every 30 minutes as a liveness heartbeat). GitHub Pages may serve a cached copy for up to 10 minutes. Judge freshness by generated_at, never by fetch time.
CORS
Served by GitHub Pages with Access-Control-Allow-Origin: * — call it directly from browser JavaScript.
Auth
None to read. The upstream DDOT key is a secret of the collection workflow and never appears in the published files.

Example response (real response, 2026-10-15; trimmed to 1 of 4 vehicles)

{
  "generated_at": "2026-10-16T00:00:29.484Z",
  "count": 4,
  "vehicles": [
    {
      "agency": "qline",
      "route_id": "QLINE",
      "vehicle_id": "287",
      "lat": 42.33913040161133,
      "lon": -83.05245971679688,
      "bearing": 154,
      "speed_mph": null,
      "updated_at": "2026-10-15T23:59:59.000Z"
    }
  ],
  "sources": {
    "ddot":  { "ok": true, "at": "2026-10-16T00:00:29.484Z", "vehicles": 0, "batches_ok": 4, "batches_failed": 0 },
    "smart": { "ok": true, "at": "2026-10-16T00:00:29.484Z", "vehicles": 141 },
    "qline": { "ok": true, "at": "2026-10-16T00:00:29.484Z", "vehicles": 4 }
  }
}

Top-level fields

FieldTypeDescription
generated_atstringISO 8601 UTC timestamp of when the publisher generated this file. The file is only republished when the data changes (30-minute heartbeat aside), so this is the freshness clock for everything in it.
countintegerNumber of vehicle objects in vehicles.
vehiclesarrayLive vehicle snapshots (fields below).
sourcesobjectPer-agency feed status, keyed ddot, smart, qline: ok, at, and either vehicles (a count) or a reason. Two states are named explicitly: sources.ddot carries "configured": false with a reason when the collection workflow lacks the upstream DDOT key — DDOT vehicles are then absent while other agencies still publish; sources.smart reports the status of the SMART display leg built from the Swiftly GTFS-Realtime feed: SMART vehicle positions are displayed in the Live Transit Detroit application only and are never part of this file's vehicles array or of the public JSON contract — the vehicles count under sources.smart counts positions published to the application's display payload, not records in this file.

Vehicle fields

FieldTypeDescription
agencystringddot or qline.
route_idstringGTFS route id: the route number for DDOT vehicles (the feed covers all 37 DDOT routes), or QLINE for the streetcar.
vehicle_idstringAgency vehicle number.
lat / lonnumberWGS84 position in decimal degrees.
bearingnumberCompass heading in degrees, or null when the feed does not report it.
speed_mphnumberReported speed in miles per hour, or null when the feed does not report it.
destinationstringDDOT records only: the destination text reported by the BusTime feed, or null. QLINE records do not carry this field.
updated_atstringVehicle timestamp as reported by the agency feed (ISO 8601; DDOT timestamps are agency-local time without an offset). null when the feed reports none.
GET /live-data/alerts.json
Parameters
None.
Refresh
Rebuilt on the same schedule as vehicles.json: about every 5 minutes when the alert set or a source state changes, with the same 30-minute heartbeat.
CORS
Served by GitHub Pages with Access-Control-Allow-Origin: *.
Auth
None to read.

Example response (real response, 2026-10-15)

{
  "generated_at": "2026-10-15T19:51:06.386Z",
  "count": 0,
  "alerts": [],
  "sources": [
    {
      "agency": "ddot",
      "kind": "realtime",
      "via": "bustime",
      "fetched_at": "2026-10-15T19:51:06.386Z",
      "ok": false,
      "status": "error",
      "alerts": 0,
      "reason": "DDOT_BUSTIME_KEY secret not configured",
      "configured": false
    }
  ]
}

Alerts top-level fields

FieldTypeDescription
generated_atstringISO 8601 UTC timestamp of when the publisher generated this file.
countintegerNumber of alert objects in alerts.
alertsarrayActive DDOT service bulletins (fields below).
sourcesarrayPer-agency feed status as an array (in vehicles.json it is an object). Each entry carries agency, kind, via, fetched_at, ok, status (ok or error), and alerts (a count); a failed or unconfigured source adds a reason, and the not-configured state adds "configured": false.

Bulletin fields

FieldTypeDescription
idstringBulletin id from the BusTime feed.
agencystringddot.
headerstringBulletin headline.
descriptionstringBulletin detail text.
effect / causestringCurrently always null — the BusTime bulletin feed does not carry GTFS-RT effect/cause values.
active_periodobject{start, end}: start is the bulletin timestamp reported by the BusTime feed (agency-local ISO 8601, or null); end is currently always null.
routes / stopsarrayRoute ids and stop ids the bulletin applies to.
urlstringCurrently always null.
sourcestringbustime.
fetched_atstringWhen the publisher fetched this bulletin (the file’s generated_at).
kindstringrealtime.
GET /feed.xml
Format
RSS 2.0 feed of the academy’s publishing, newest first: The Signal editions and specials, Live Transit Detroit installments, Detroit 2126, and Notes. Each item carries the article’s title, link, publication date, category, and description, plus a content:encoded body with links back to the article and its section hub.
Parameters
None.
Refresh
Regenerated from sitemap.xml and each page’s own metadata when a new article publishes. Point a feed reader at the URL and new pieces arrive as they publish; there is no polling schedule.
CORS
Served by GitHub Pages with Access-Control-Allow-Origin: *.
Auth
None. No key required.
Display
Feed readers receive the raw RSS XML. Opened in a browser, the feed renders as a formatted page via /feed.xsl, the XSLT stylesheet the feed declares — the stylesheet affects browser display only.

Retired endpoints

The live data used to be served by a proxy at forge-line-live-proxy.netlify.app. Those endpoints were retired on 2026-10-15 — the proxy’s hosting quota was exhausted (it returned HTTP 503 usage_exceeded on every path) and the feed moved to the static files above. Do not build against them:

  • GET https://forge-line-live-proxy.netlify.app/api/vehicles — retired 2026-10-15; use /live-data/vehicles.json instead.
  • GET https://forge-line-live-proxy.netlify.app/api/alerts — retired 2026-10-15; use /live-data/alerts.json instead.
  • GET https://forge-line-live-proxy.netlify.app/healthz — retired 2026-10-15; the static files need no health endpoint — check generated_at.

Rate-limit etiquette

  • Poll at most once per minute. Positions poll continuously and publish about every five minutes; the map dead-reckons between updates — matching that cadence is plenty.
  • The files are rebuilt about every 5 minutes, so faster polling returns the same file; use generated_at to detect genuinely new data instead of re-polling.
  • Cache responses client-side. Don’t fan out one page view into many parallel requests.
  • The published files are the whole interface — there is no key to ask for and nothing upstream to bypass; the agency key exists only inside the collection workflow.

Building a student project or a community tool? The 5-minute cadence is the intended use. If you need something the feed doesn’t offer, the logged history below may already have it.

Use of these files — and of the logged history dataset below — is governed by §6 of the Terms of Service.

Commercial use

Personal, student, and community use is free with attribution: Live transit data: Detroit Automation Academy (detroitautomationacademy.com). Building something commercial on this data — a product, a service, a paid integration? Talk to us first: justin@detroitautomationacademy.com. Commercial use is licensed by AUTOMATEDTECHNOLOGIES; one conversation sets the terms.

Logged history dataset

A public log of this feed’s snapshots, appended every 5 minutes, runs from 2026-10-03 — the raw material for headway analysis, reliability scores, and arrival predictions. Vehicle objects in the log use exactly the same field names as the live endpoint above, so code written against the live feed reads the history without translation.

  • Repository: github.com/smit4786/forge-line-transit-data
  • Format: forge-line.telemetry/v1, documented in the repo’s SCHEMA.md
  • Day index: https://raw.githubusercontent.com/smit4786/forge-line-transit-data/main/data/index.json
  • One day of polls: https://raw.githubusercontent.com/smit4786/forge-line-transit-data/main/data/2026-10-03.jsonl (replace the date)

Failed polls are never logged as empty records — a gap in a day’s file means that poll failed, not that zero buses were running.

Error behavior

  • Old generated_at — the files are static, so a failed or delayed build leaves the previous file in place rather than returning an error. If generated_at is well past the ~5-minute publish cadence, treat the contents as of that timestamp and keep retrying on your normal cadence.
  • A source reports "ok": false — that agency’s leg failed or is not configured (its reason says which); the other agencies’ data still publishes. Check sources before concluding the whole feed is down.
  • Non-200 or network failure — back off and retry after 60 seconds. Don’t hammer the files; they only change every few minutes anyway.
  • Empty vehicles array — valid response, no vehicles currently reporting on the covered routes (common late at night).

File-level timestamps (generated_at, fetched_at) are UTC; vehicle and bulletin timestamps are reported as the agency feeds give them. Route coverage may expand as feeds are added; check sources to see which agencies are live.

Attribution

Built by Detroit Automation Academy.

Live vehicle data: DDOT via Clever Devices BusTime; QLINE via its public GTFS-RT feed. Route shapes and stops: DDOT and QLINE GTFS.

This feed is a community resource, published from the academy’s own repository — please follow the rate-limit etiquette above so it stays fast for everyone.