The public API.
The same data the apps and this website use. No key required, open CORS, fair limits.
Ground rules
https://api.oeffigo.appAccess-Control-Allow-Origin: *.429 with Retry-After: 30.no-store. Successes vary with how fast the data moves: max-age=10 as the rule, 15 for /v1/vehicles, 60 in the browser and 300 at the edge for /v1/history. POST /v1/predictions/batch is no-store — its answer depends on the body you sent and belongs in no shared cache. Trust the header, not this paragraph./v2 is versioned separately and names its version on every response in the X-OeffiGo-Contract header./v2 is finished — fifteen routes, a versioned contract, a machine-readable schema at /v2/schema and the route list at /v2/meta. Both are open to read. You still cannot call the routes from outside: they refuse any request carrying an Origin and require an installation token only the apps get. That is deliberate — behind /v2 sits a contractually capped daily allowance at the data provider, and an open route on top of it would be a route that eventually runs empty for everyone./v1 endpoints documented here are the open surface: plain GETs, CORS allowed, usable without credentials. They stay. They are not what the apps speak — those moved to /v2 — but that is also why they do not sit on the provider allowance. Extra fields can appear at any time; ignore fields you do not know.{ "error": "code" } with a matching HTTP status.Endpoints
Lightweight liveness check.
Returns 200 while the worker has a recent successful cycle, otherwise 503. Meant for monitoring, not for fetching data.
Request
curl -s https://api.oeffigo.app/v1/healthResponse
{
"ok": true,
"version": "5dc1a29",
"predictionsLive": 1312,
"confidentPredictions": 1190
}Operational state of every component.
Returns the overall state, a component list and a half-hourly availability history per data path. This site's status page reads the same source.
Request
curl -s https://api.oeffigo.app/v1/statusResponse
{
"generatedAt": "2026-07-26T13:25:06.754Z",
"overall": "operational",
"components": [
{ "id": "api", "name": "…", "status": "operational" }
],
"measurement": { "mode": "official_realtime" }
}Reliability analysis computed from official actual times.
Totals, a daily trend, lines and a weekday×hour pattern. Individual stop events are counted, not whole journeys. A missing data point is never treated as a cancellation — cancellations come only from explicit events.
| Parameter | Type | Meaning |
|---|---|---|
from | ISO date | Start of the period. |
to | ISO date | End of the period. At most a 366-day span. |
line | string | Restrict to a single line. |
source | string | Restrict to one data source: wiener_linien, trias, stmk_gtfs_rt. |
Request
curl -s "https://api.oeffigo.app/v1/history?from=2026-07-15&to=2026-07-26&line=U1"Response
{
"period": { "from": "2026-07-15", "to": "2026-07-26" },
"totals": {
"onTimeShare": 0.9556,
"avgDelayMin": 0.57,
"late10Share": 0.007
},
"officialRealtime": {
"daily": [ { "day": "2026-07-26", "events": 18109, "on_time_pct": 97.4 } ],
"lines": [ { "line": "U1", "events": 39970, "on_time_pct": 96 } ],
"heatmap": [ { "dow": 1, "hour": 8, "avg_delay_min": 0.6 } ]
}
}- Cacheable at the edge for five minutes; supports ETag and 304.
- `dow` is ISO: 1 = Monday … 7 = Sunday.
Our own delay estimates derived from official realtime.
High and medium confidence entries only. Every entry expires at most eight minutes after its last real observation; older values are never served. <strong>A filter is required</strong>: without one you get <code>400 missing_query</code>, because "every prediction" is not a question this endpoint answers.
| Parameter | Type | Meaning |
|---|---|---|
lat + lon | number | A radius search, optionally with radius. One of the four filters. |
line | string | Line label, e.g. U1. One of the four filters. |
jid | string | One journey reference. One of the four filters. |
jids | string[] | Up to 40 journey references, comma separated. One of the four filters. |
radius | int | Only together with lat + lon. |
limit | int | At most 200. Does not substitute for a filter. |
Request
curl -s "https://api.oeffigo.app/v1/predictions?line=U1&limit=50" -H 'If-None-Match: "abc123"'Response
{
"generatedAt": "2026-07-26T13:25:06.754Z",
"count": 2,
"predictions": [
{ "jid": "og1.…", "delayMin": 2, "confidence": "high" }
]
}- Responses carry a weak ETag. With If-None-Match you get a body-less 304 while the snapshot is unchanged — the battery-friendly way to poll.
Look up many journey references at once.
Journey references are long and do not belong in a URL. For more than a handful, this is the right call.
Request
curl -s -X POST https://api.oeffigo.app/v1/predictions/batch \
-H 'content-type: application/json' \
-d '{"jids":["og1.…","og1.…"]}'Response
{
"generatedAt": "2026-07-26T13:25:06.754Z",
"count": 2,
"predictions": [ … ]
}A bounded map viewport of vehicles.
Requires either a bounding box or a list of journey references — there is no unfiltered response. Entries are at most eight minutes old. The `source` field tells you what an empty result means.
| Parameter | Type | Meaning |
|---|---|---|
minLat, minLon, maxLat, maxLon | number | Visible map viewport — all four or none. |
jids | string[] | Comma-separated journey references. The server accepts up to 500; in practice URL length caps it around 40. Does not trigger a fetch. |
limit | number | 1–1,000, default 500. |
Request
curl -s "https://api.oeffigo.app/v1/vehicles?minLat=48.18&minLon=16.30&maxLat=48.24&maxLon=16.42"Response
{
"generatedAt": "2026-08-03T19:25:06.754Z",
"count": 242,
"source": "ondemand_live",
"measurementMode": "official_realtime",
"vehicles": [ { "jid": "og1.…", "lat": 48.2, "lon": 16.37 } ]
}- `ondemand_live` with an empty list means "no vehicles right now", not "service off". `ondemand_capped` means "live vehicles temporarily unavailable".
Vienna metro realtime, cached.
Only stations from a fixed list — the endpoint is deliberately not an open proxy onto the operator monitor. Data source: City of Vienna, CC BY 4.0.
| Parameter | Type | Meaning |
|---|---|---|
diva | int | Station identifier from the list. |
Request
curl -s "https://api.oeffigo.app/v1/wiener-linien/metro?diva=60200657"Response
{
"generatedAt": "2026-08-03T19:16:51.814Z",
"status": "ok",
"departures": [
{ "line": "1", "towards": "Stefan-Fadinger-Platz",
"plannedAt": "2026-08-03T19:23:30.000Z",
"realAt": "2026-08-03T19:26:50.000Z" }
]
}- The delay is not a field — it is the difference between
realAtandplannedAt. Both are ISO timestamps, not milliseconds. - Can answer
503 source_standbywhen the Wiener Linien source is switched off. That is a deliberate brake, not an outage — try again later.
Server-driven messages for the apps.
Lets us publish notices without shipping an app update. Closed and expired messages are not included.
Request
curl -s https://api.oeffigo.app/v1/announcementsResponse
{
"generatedAt": "2026-07-26T13:25:06.754Z",
"announcements": [ { "id": "…", "title": "…", "body": "…" } ]
}What the API deliberately is not.
The typed routes the apps use to fetch timetable and journey data are bound to the apps and are not part of this documentation. They are not a general proxy onto third-party providers, and their credentials never leave the server. Please do not build anything that depends on them.
Journey references (og1.…) are opaque and provider-bound. Do not unpack, rewrite or mix them across networks — pass them back unchanged.
The API is open so people can build with it. Stay inside the limits, cache responses, and if you reuse Vienna realtime data, credit the source: City of Vienna — data.wien.gv.at, CC BY 4.0.