Waitlist only · the first 100 get a double trial + 15% off the first monthWaitlist only · the first 100 get a double trial + 15% off the first monthWaitlist only · the first 100 get a double trial + 15% off the first monthWaitlist only · the first 100 get a double trial + 15% off the first monthWaitlist only · the first 100 get a double trial + 15% off the first monthWaitlist only · the first 100 get a double trial + 15% off the first month

Developers

The public ÖffiGo API

Everything the measurement network produces live is also available as an open JSON API — the same one the app and the status page use. No key, no sign-up; please use it fairly (limits below).

Base URL: https://api.oeffigo.app
EndpointDescription
GET /v1/healthQuick health check of the API
GET /v1/statusStatus of all system components (basis of the status page)
GET /v1/predictionsLive delay predictions from the measurement network
GET /v1/announcementsActive service announcements
GET /v1/historyAggregated reliability history (delays by line, stop, hour)

Ground rules

GET /v1/health

Responds 200 when the last measurement pass is fresh, otherwise 503.

{
  "ok": true,
  "vehiclesLive": 1312,
  "lastCycleAt": "2026-07-03T10:15:30.000Z"
}

GET /v1/status

Component status (measurement, data source, Wiener Linien, traffic, database, API) with plain-text details in German — overall is operational, degraded or down. This is exactly what the status page renders.

{
  "generatedAt": "2026-07-03T10:15:35.000Z",
  "overall": "operational",
  "components": [
    {
      "id": "ingestion",
      "name": "Messung (Fahrzeug-Beobachtung)",
      "status": "operational",
      "detail": "Letzter Messdurchlauf vor 28 s, 1.312 Fahrzeuge."
    }
  ]
}

GET /v1/predictions

Live delay predictions from the measurement network's in-memory snapshot. One lookup mode per request:

ParameterMeaning
lat + lonPredictions around a coordinate. Optional radius in metres (default 2000, max 25000) and limit (default 50, max 200).
lineExact line label, case-insensitive. Combinable with lat/lon.
jidDirect journey ID → exactly one prediction or 404.
jidsComma-separated journey IDs (max 40) for a whole departure board.
curl 'https://api.oeffigo.app/v1/predictions?lat=47.0707&lon=15.4395&radius=3000&limit=20'
{
  "generatedAt": "2026-07-03T10:15:35.000Z",
  "count": 1,
  "predictions": [
    {
      "jid": "1|12345|0|80|03072026",
      "line": "U1",
      "product": "U-Bahn",
      "direction": "Leopoldau",
      "lat": 48.2084,
      "lon": 16.3725,
      "delayMin": 1.2,
      "feedDelayMin": 1,
      "confidence": "high",
      "progress": 0.642,
      "updatedAt": "2026-07-03T10:15:30.000Z",
      "distanceM": 116
    }
  ]
}

delayMin is our own prediction in minutes, feedDelayMin the official display (or null if there is none), confidence the model's certainty. Predictions expire ~3 minutes after the last refresh.

GET /v1/announcements

Active service announcements (e.g. maintenance windows), max 20, newest first.

{
  "generatedAt": "2026-07-03T10:15:35.000Z",
  "announcements": [
    {
      "id": 42,
      "severity": "warning",
      "title": "S-Bahn Einschränkung",
      "body": "Auf der Stammstrecke kommt es derzeit zu Verzögerungen.",
      "url": null,
      "startsAt": "2026-07-03T09:00:00.000Z",
      "endsAt": null
    }
  ]
}

GET /v1/history

Aggregated reliability history for Austria's public transport — the data behind the reliability atlas. It reads pre-aggregated daily tables (5-minute server cache + CDN), so a request never touches raw data. All spans are day-based:

ParameterMeaning
from + toDate range as YYYY-MM-DD. Defaults to the last 90 days; the span may not exceed 366 days.
lineExact line label (e.g. S1), matched against the stored label. Max 80 chars.
productFilter by mode (e.g. Bus, Straßenbahn, S-Bahn). Max 40 chars.
curl 'https://api.oeffigo.app/v1/history?from=2026-06-01&to=2026-07-12&product=S-Bahn'
{
  "generatedAt": "2026-07-12T08:15:00.000Z",
  "period": { "from": "2026-06-01", "to": "2026-07-12" },
  "dataSince": "2026-06-20",
  "coverage": {
    "journeys": 48213,
    "samples": 912847,
    "days": 23,
    "cancellationCoverage": "explicit_events_only"
  },
  "totals": {
    "onTimeShare": 0.8412,
    "avgDelayMin": 1.83,
    "late3Share": 0.1104,
    "late10Share": 0.0212,
    "cancellationRate": 0.0038
  },
  "daily": [
    { "day": "2026-06-20", "journeys": 2104, "samples": 39218, "avg_delay_min": 1.71, "on_time_pct": 85.2, "late_10_pct": 1.9 }
  ],
  "lines": [
    { "line": "S1", "product": "S-Bahn", "journeys": 1842, "samples": 41230, "avg_delay_min": 2.44, "on_time_pct": 81.0, "late_3_pct": 13.2, "late_10_pct": 3.1, "p90_delay_min": 6.0 }
  ],
  "stops": [
    { "stop_id": "900068", "stop_name": "Graz Hauptbahnhof", "journeys": 5120, "samples": 88210, "lines": 22, "avg_delay_min": 2.01, "on_time_pct": 83.4, "late_10_pct": 2.6, "p90_delay_min": 5.0 }
  ],
  "heatmap": [
    { "dow": 1, "hour": 7, "journeys": 412, "avg_delay_min": 2.9, "on_time_pct": 78.1 }
  ],
  "products": [
    { "product": "S-Bahn", "journeys": 21044, "avg_delay_min": 1.83, "on_time_pct": 84.1 }
  ],
  "serviceEvents": [
    { "event_type": "cancelled", "day": "2026-07-02", "events": 7 }
  ]
}

Every block carries its own sample size (journeys/samples) — a long GPS trace never counts more than a short journey, and cancellations appear only from explicit disruption/cancel events (cancellationCoverage), never inferred from a missing vehicle. Supports If-None-Match304.

Questions?

The API is young and grows with the measurement network. If you build something with it or a field is missing: get in touch.