00Developersv1

The public API.

The same data the apps and this website use. No key required, open CORS, fair limits.

Ground rules

Base URLhttps://api.oeffigo.app
CORSOpen — every response carries Access-Control-Allow-Origin: *.
Rate limit120 requests per minute per IP. Beyond that you get 429 with Retry-After: 30.
CachingErrors are always 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.
VersioningBreaking changes get a new path. Extra fields can appear at any time — ignore fields you do not know. The app contract on /v2 is versioned separately and names its version on every response in the X-OeffiGo-Contract header.
<code>/v2</code> is the app contract, not an open API/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.
What that means for <code>/v1</code>The /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 shapeAlways { "error": "code" } with a matching HTTP status.

Endpoints8

01GET/v1/health

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/health

Response

{
  "ok": true,
  "version": "5dc1a29",
  "predictionsLive": 1312,
  "confidentPredictions": 1190
}
02GET/v1/status

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/status

Response

{
  "generatedAt": "2026-07-26T13:25:06.754Z",
  "overall": "operational",
  "components": [
    { "id": "api", "name": "…", "status": "operational" }
  ],
  "measurement": { "mode": "official_realtime" }
}
03GET/v1/history

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.

ParameterTypeMeaning
fromISO dateStart of the period.
toISO dateEnd of the period. At most a 366-day span.
linestringRestrict to a single line.
sourcestringRestrict 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 } ]
  }
}
04GET/v1/predictions

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.

ParameterTypeMeaning
lat + lonnumberA radius search, optionally with radius. One of the four filters.
linestringLine label, e.g. U1. One of the four filters.
jidstringOne journey reference. One of the four filters.
jidsstring[]Up to 40 journey references, comma separated. One of the four filters.
radiusintOnly together with lat + lon.
limitintAt 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" }
  ]
}
05POST/v1/predictions/batch

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": [ … ]
}
06GET/v1/vehicles

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.

ParameterTypeMeaning
minLat, minLon, maxLat, maxLonnumberVisible map viewport — all four or none.
jidsstring[]Comma-separated journey references. The server accepts up to 500; in practice URL length caps it around 40. Does not trigger a fetch.
limitnumber1–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 } ]
}
07GET/v1/wiener-linien/metro

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.

ParameterTypeMeaning
divaintStation 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" }
  ]
}
08GET/v1/announcements

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/announcements

Response

{
  "generatedAt": "2026-07-26T13:25:06.754Z",
  "announcements": [ { "id": "…", "title": "…", "body": "…" } ]
}
09Not public

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.

10Usage & attribution

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.