Developer
Menü

Bahnsteig

Ein Gleis, wie es die Anzeigen von bahnhoftafel.de sehen: wie weit der nächste Zug noch weg ist, welche Baureihe kommt und wo ein Kurzzug hält, die Hinweise im Laufband, Zugteilungen und Wagenreihung. Dazu die Bahnsteigansagen als Text und Tonaufnahme (mit Freischaltung), die Gehzeit zum Bahnhof und der QR-Code zur Bahnhofsseite.

Objekte

Objekt Bahnsteig

FeldTypBeschreibung
stationobject{ id, name }, bei Berlin der Bahnhof der S-Bahn
platform?stringGleis
stylestringsbahn_berlin (S-Bahn Berlin mit Fahrzeugdaten) oder db (DB-Anzeiger mit Hinweisen und Wagenreihung)
generated_atstringStand der Daten
station_page?stringSeite des Bahnhofs auf bahnhof.de
serviceobject{ suspended, replacement_service, ticker }: Verkehr eingestellt, Ersatzverkehr, Texte im Laufband
trainsZug am Gleis[]Züge in der Reihenfolge am Gleis
{
  "station": {
    "id": "8089028",
    "name": "Ostkreuz"
  },
  "platform": "3",
  "style": "sbahn_berlin",
  "generated_at": "2026-10-15T20:26:19+02:00",
  "station_page": "https://www.bahnhof.de/berlin-ostkreuz",
  "service": {
    "suspended": false,
    "replacement_service": null,
    "ticker": [
      "Nach einem Polizeieinsatz in Heerstraße wird der durchgehende Zugverkehr wieder aufgenommen."
    ]
  },
  "trains": [
    {
      "trip_id": "dmJiHzF8MjA1OHwwfDg2fDE1MTAyMDI2",
      "train": {
        "name": "S5",
        "line": "S5",
        "number": "19562",
        "product": "suburban",
        "color": {
          "background": "#eb7405",
          "text": "#ffffff",
          "shape": "pill"
        }
      },
      "destination": "Mahlsdorf",
      "origin": null,
      "via": [
        "Lichtenberg",
        "Friedrichsfelde Ost"
      ],
      "via_announced": [
        "Lichtenberg",
        "Friedrichsfelde Ost"
      ],
      "route": [],
      "departure": {
        "scheduled": "2026-10-15T20:27:00+02:00",
        "expected": "2026-10-15T20:27:00+02:00",
        "delay": 0,
        "prognosis": "prognose"
      },
      "arrival": {
        "scheduled": "2026-10-15T20:26:00+02:00",
        "expected": "2026-10-15T20:26:00+02:00",
        "estimated": false
      },
      "platform": {
        "scheduled": "3",
        "actual": "3",
        "changed": false
      },
      "cancelled": false,
      "additional": false,
      "starts_here": false,
      "ends_here": false,
      "approach": {
        "distance_m": 1258,
        "speed_kmh": 43,
        "state": "entering",
        "measured_at": "2026-10-15T20:26:19+02:00"
      },
      "vehicle": {
        "series": "481",
        "length": "full",
        "units": 4,
        "cars": 8,
        "stops_in_sections": null,
        "diagram_url": "https://fahrinfo.vbb.de/hafas-res/img/products/S-Bahn_BR481_Vollzug.png"
      },
      "occupancy": "low",
      "notices": [],
      "split": null,
      "bicycle_reservation": false,
      "previous_arrival": null,
      "coach_sequence": null,
      "realtime_source": "gtfsrt"
    }
  ]
}

Objekt Zug am Gleis

FeldTypBeschreibung
trip_id?stringFür Fahrt abrufen, bei style=sbahn_berlin
trainobject{ name, line, number, product, color }, color ist { background, text, shape } wie das Linienzeichen
destination?stringZiel; null, wenn der Zug hier endet
origin?stringHerkunft, nur wenn der Zug hier endet
viastring[]Halte, wie sie auf dem Anzeiger stehen
via_announcedstring[]Halte, die in der Ansage genannt werden (Umsteigeknoten)
routestring[]Alle folgenden Halte (DB-Anzeiger)
departure?object{ scheduled, expected, delay, prognosis }
arrival?object{ scheduled, expected, estimated }
platformobject{ scheduled, actual, changed }
cancelledbooleanZug fällt aus
additionalbooleanZusätzlicher Zug
starts_herebooleanZug beginnt hier
ends_herebooleanZug endet hier
approach?object{ distance_m, speed_kmh, state, measured_at }: Weg bis zum Halteplatz und Tempo. state: approaching (unterwegs), entering (fährt ein, unter 2 km), standing (steht am Bahnsteig), stopped (steht unterwegs, z. B. am Signal oder am Bahnhof davor), receding (entfernt sich). Nur S-Bahn Berlin
vehicle?object{ series, length, units, cars, stops_in_sections }: Baureihe (z. B. 481), full oder short, Viertelzüge, Wagen; bei Kurzzügen die Abschnitte { from, to, label }; diagram_url: Zugbild des VBB (siehe Baureihen)
occupancy?stringAuslastung (low, medium, high)
noticesobject[]Hinweise im Laufband { text, symbol }, z. B. Ersatzzug, Halt entfällt, Fahrradmitnahme
split?objectZugteilung { at, parts: [{ train, destination }] }
bicycle_reservationbooleanFahrradmitnahme reservierungspflichtig
previous_arrival?objectDer Zug kommt als { time, origin } hier an und fährt dann weiter
coach_sequence?objectWagenreihung in Prozent der Bahnsteiglänge: { direction, sectors, coaches, parts }
realtime_source?stringgtfsrt (Fahrzeugdaten der S-Bahn), hafas oder iris
{
  "trip_id": "dmJiHzF8MjA1OHwwfDg2fDE1MTAyMDI2",
  "train": {
    "name": "S5",
    "line": "S5",
    "number": "19562",
    "product": "suburban",
    "color": {
      "background": "#eb7405",
      "text": "#ffffff",
      "shape": "pill"
    }
  },
  "destination": "Mahlsdorf",
  "origin": null,
  "via": [
    "Lichtenberg",
    "Friedrichsfelde Ost"
  ],
  "via_announced": [
    "Lichtenberg",
    "Friedrichsfelde Ost"
  ],
  "route": [],
  "departure": {
    "scheduled": "2026-10-15T20:27:00+02:00",
    "expected": "2026-10-15T20:27:00+02:00",
    "delay": 0,
    "prognosis": "prognose"
  },
  "arrival": {
    "scheduled": "2026-10-15T20:26:00+02:00",
    "expected": "2026-10-15T20:26:00+02:00",
    "estimated": false
  },
  "platform": {
    "scheduled": "3",
    "actual": "3",
    "changed": false
  },
  "cancelled": false,
  "additional": false,
  "starts_here": false,
  "ends_here": false,
  "approach": {
    "distance_m": 1258,
    "speed_kmh": 43,
    "state": "entering",
    "measured_at": "2026-10-15T20:26:19+02:00"
  },
  "vehicle": {
    "series": "481",
    "length": "full",
    "units": 4,
    "cars": 8,
    "stops_in_sections": null,
    "diagram_url": "https://fahrinfo.vbb.de/hafas-res/img/products/S-Bahn_BR481_Vollzug.png"
  },
  "occupancy": "low",
  "notices": [],
  "split": null,
  "bicycle_reservation": false,
  "previous_arrival": null,
  "coach_sequence": null,
  "realtime_source": "gtfsrt"
}

Objekt Ansage

FeldTypBeschreibung
idstringKennung; dieselbe Ansage kommt nie zweimal
kindstringeinfahrt, eingefahren, endet, bereit, verspaetung, entfall, gleiswechsel, zusatzzug, verkehrt, stoerung, anschluss, information, hinweis …
priority?integerVorrang, 100 = Einfahrt, 10 = allgemeiner Hinweis
text?stringDer gesprochene Wortlaut
trip_id?stringFahrt, um die es geht
spoken_atstringWann die Ansage fällig wurde
valid_until?stringBis wann sie noch zutrifft
languagestringde
audio_url?stringTonaufnahme (MP3), eine Zeit lang abspielbar, ohne Schlüssel
followupsobject[]Folgeansagen in anderen Sprachen { language, audio_url }
speakerstringSprecherin der Aufnahmen: Alisa Palmer
usagestringprivate: die Aufnahmen sind nur für den privaten Gebrauch zulässig
{
  "id": "Qm9xZk1nT2xXc1hE",
  "kind": "einfahrt",
  "priority": 100,
  "text": "Gleis 3, Einfahrt S5 nach Mahlsdorf. Vorsicht bei der Einfahrt.",
  "trip_id": "dmJiHzF8MjA1OHwwfDg2fDE1MTAyMDI2",
  "spoken_at": "2026-10-15T20:25:48+02:00",
  "valid_until": "2026-10-15T20:26:30+02:00",
  "language": "de",
  "audio_url": "https://api.bahnhoftafel.de/v1/announcements/audio?q=…&exp=1792090548&sig=…",
  "followups": [],
  "speaker": "Alisa Palmer",
  "usage": "private"
}

Bahnsteig jetzt

GET/v1/stations/{station_id}/platforms/{platform}Eimer standard

Was gerade an einem Gleis los ist. An S-Bahn-Gleisen in Berlin mit Abstand und Tempo des einfahrenden Zuges, Baureihe, Zuglänge und den Abschnitten, in denen ein Kurzzug hält. Sonst wie der DB-Anzeiger: Hinweise im Laufband, Zugteilungen, Wagenreihung, „kommt als … aus …“.

Info

Die Daten sind sehr frisch. Für laufende Anzeigen ist Bahnsteig live besser.

Pfad-Parameter

FeldTypBeschreibung
station_idPflichtstringEVA-Nummer
platformPflichtstringGleis, z. B. 3 oder 11a

Antwort

Bahnsteig

{
  "station": {
    "id": "8089028",
    "name": "Ostkreuz"
  },
  "platform": "3",
  "style": "sbahn_berlin",
  "generated_at": "2026-10-15T20:26:19+02:00",
  "station_page": "https://www.bahnhof.de/berlin-ostkreuz",
  "service": {
    "suspended": false,
    "replacement_service": null,
    "ticker": [
      "Nach einem Polizeieinsatz in Heerstraße wird der durchgehende Zugverkehr wieder aufgenommen."
    ]
  },
  "trains": [
    {
      "trip_id": "dmJiHzF8MjA1OHwwfDg2fDE1MTAyMDI2",
      "train": {
        "name": "S5",
        "line": "S5",
        "number": "19562",
        "product": "suburban",
        "color": {
          "background": "#eb7405",
          "text": "#ffffff",
          "shape": "pill"
        }
      },
      "destination": "Mahlsdorf",
      "origin": null,
      "via": [
        "Lichtenberg",
        "Friedrichsfelde Ost"
      ],
      "via_announced": [
        "Lichtenberg",
        "Friedrichsfelde Ost"
      ],
      "route": [],
      "departure": {
        "scheduled": "2026-10-15T20:27:00+02:00",
        "expected": "2026-10-15T20:27:00+02:00",
        "delay": 0,
        "prognosis": "prognose"
      },
      "arrival": {
        "scheduled": "2026-10-15T20:26:00+02:00",
        "expected": "2026-10-15T20:26:00+02:00",
        "estimated": false
      },
      "platform": {
        "scheduled": "3",
        "actual": "3",
        "changed": false
      },
      "cancelled": false,
      "additional": false,
      "starts_here": false,
      "ends_here": false,
      "approach": {
        "distance_m": 1258,
        "speed_kmh": 43,
        "state": "entering",
        "measured_at": "2026-10-15T20:26:19+02:00"
      },
      "vehicle": {
        "series": "481",
        "length": "full",
        "units": 4,
        "cars": 8,
        "stops_in_sections": null,
        "diagram_url": "https://fahrinfo.vbb.de/hafas-res/img/products/S-Bahn_BR481_Vollzug.png"
      },
      "occupancy": "low",
      "notices": [],
      "split": null,
      "bicycle_reservation": false,
      "previous_arrival": null,
      "coach_sequence": null,
      "realtime_source": "gtfsrt"
    }
  ]
}

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.BT_SCHLUESSEL}`, },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3",
    headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
    timeout=30,
)
daten = antwort.json()

Bahnsteig live

GET/v1/stations/{station_id}/platforms/{platform}/liveEimer live

Server-Sent Events. platform mit dem Bahnsteig, sobald sich etwas ändert, auch wenn der einfahrende Zug näher kommt. ping in regelmäßigen Abständen, end nach einer Weile.

Achtung

Zählt zu den 2 gleichzeitigen Live-Strömen je App.

Pfad-Parameter

FeldTypBeschreibung
station_idPflichtstringEVA-Nummer
platformPflichtstringGleis, z. B. 3 oder 11a

Antwort

text/event-stream

event: platform
data: {"station":{"id":"8089028","name":"Ostkreuz"},"platform":"3","trains":[…]}

event: ping
data: {}

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/live" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/live", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.BT_SCHLUESSEL}`, },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/live",
    headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
    timeout=30,
)
daten = antwort.json()

Ansagen live

GET/v1/stations/{station_id}/platforms/{platform}/announcementsEimer live

Server-Sent Events. announcement mit einer Ansage, sobald sie am Bahnsteig fällig ist: Einfahrt, Verspätung, Gleiswechsel, Ausfall, zusätzlicher Zug, Sicherheitshinweis. Mit Wortlaut und Tonaufnahme. ping in regelmäßigen Abständen, end nach einer Weile.

Achtung

Nur mit einem Ansage-Schlüssel. Den gibt es nach einer Freischaltung: im Discord in #freischalten auf „Ansage-API anfragen“ klicken und das Formular ausfüllen; nach der Genehmigung klickst du im Ticket auf „Freischaltung abholen“ und legst ihn im Portal an. Normale App-Schlüssel bekommen hier 403 forbidden.

Info

Ansagen gibt es an S-Bahn-Gleisen in Berlin. Jede Ansage kommt genau einmal; wer sie verpasst, bekommt sie nicht nachgeliefert.

Pfad-Parameter

FeldTypBeschreibung
station_idPflichtstringEVA-Nummer
platformPflichtstringGleis, z. B. 3 oder 11a

Antwort

text/event-stream

event: announcement
data: {"id":"Qm9xZk1nT2xXc1hE","kind":"einfahrt","priority":100,"text":"Gleis 3, Einfahrt S5 nach Mahlsdorf. Vorsicht bei der Einfahrt.","trip_id":"dmJiHzF8MjA1OHwwfDg2fDE1MTAyMDI2","spoken_at":"2026-10-15T20:25:48+02:00","valid_until":"2026-10-15T20:26:30+02:00","language":"de","audio_url":"https://api.bahnhoftafel.de/v1/announcements/audio?q=…&exp=1792090548&sig=…","followups":[],"speaker":"Alisa Palmer","usage":"private"}

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/announcements" \
  -H "Authorization: Bearer $BT_ANSAGE_SCHLUESSEL"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/announcements", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.BT_ANSAGE_SCHLUESSEL}`, },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/announcements",
    headers={"Authorization": f"Bearer {os.environ['BT_ANSAGE_SCHLUESSEL']}"},
    timeout=30,
)
daten = antwort.json()

Ansage anhören

GET/v1/announcements/audioohne Schlüssel

Die Tonaufnahme einer Ansage. Die Adresse steht fertig in audio_url und lässt sich direkt in <audio src> verwenden; einen Schlüssel braucht sie nicht. Selbst bauen kann man sie nicht: sie ist signiert und 30 Minuten gültig.

Info

Ansagesprecherin: Alisa Palmer. Die Aufnahmen sind nur für den privaten Gebrauch zulässig: nicht weiterverbreiten, nicht in Videos, Streams oder Produkten verwenden.

Info

Eigener Eimer ton: 20 Aufnahmen pro Minute je Adresse. Abgelaufene oder veränderte Links: 403 forbidden.

Query-Parameter

FeldTypBeschreibung
qPflichtstringaus audio_url
expPflichtintegeraus audio_url0 bis 99999999999
sigPflichtstringaus audio_url

Antwort

audio/mpeg

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/announcements/audio?q=wert&exp=wert&sig=wert"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/announcements/audio?q=wert&exp=wert&sig=wert", {
  method: "GET",
  headers: { },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/announcements/audio?q=wert&exp=wert&sig=wert",
    timeout=30,
)
daten = antwort.json()

Losgehen

GET/v1/stations/{station_id}/walkEimer standard

Wann muss ich los? Gehzeit von einem Ort zum Bahnhof (Fußwege aus OpenStreetMap, sonst geschätzt) und für die nächsten Abfahrten die Minuten bis zum Loslaufen. Wie der Modus „Losgehen“ der Anzeigen.

Info

Startpunkte über 15 km Luftlinie werden abgelehnt. Die Gehzeit merken wir uns eine Weile; der Startpunkt wird dafür leicht gerundet.

Pfad-Parameter

FeldTypBeschreibung
station_idPflichtstringEVA-Nummer

Query-Parameter

FeldTypBeschreibung
latPflichtnumberBreite des Startpunkts47 bis 56
lonPflichtnumberLänge des Startpunkts5 bis 16
platformstring[]Nur diese Gleise
productsstring[]Nur diese Produktelong_distance Fernverkehr, regional Regionalverkehr, suburban S-Bahn
bufferintegerPuffer in Minuten (Fahrkarte, Treppe)Standard: 20 bis 15
announcementboolAnsagetext je Abfahrt in announcementStandard: true

Antwort

Objekt { station, walk: { minutes, meters, source }, departures: [{ …Abfahrt, leave_at, leave_in_minutes, reachable, announcement }] }. announcement ist der Ansagetext wie am Bahnsteig: an S-Bahn- und U-Bahn-Gleisen „Zug S3 nach Erkner fährt in 3 Minuten auf Gleis 8.“, sonst wie bei der DB „Zug RE 1 nach Potsdam Hbf über Berlin-Spandau, Abfahrt um 17:22 auf Gleis 7.“ Dazu je nach Lage: „heute auf Gleis 7“ bei Gleiswechsel, die erwartete Zeit und der Grund bei Verspätung, „fällt heute aus“, vorzeitiges Ende und entfallende Halte. Ausgefallene Züge stehen mit reachable: false und ohne leave_at in der Liste.

{
  "station": {
    "id": "8011162",
    "name": "Berlin Ostkreuz"
  },
  "walk": {
    "minutes": 9,
    "meters": 668,
    "source": "openstreetmap"
  },
  "departures": [
    {
      "train": {
        "name": "S3",
        "line": "S3",
        "product": "suburban"
      },
      "destination": "Erkner",
      "platform": {
        "scheduled": "3",
        "actual": "3",
        "changed": false
      },
      "departure": {
        "scheduled": "2026-10-15T20:41:00+02:00",
        "actual": "2026-10-15T20:42:00+02:00",
        "delay": 1
      },
      "leave_at": "2026-10-15T20:31:00+02:00",
      "leave_in_minutes": 4,
      "reachable": true,
      "announcement": "Zug S3 nach Erkner fährt in 13 Minuten auf Gleis 3."
    }
  ]
}

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/walk?lat=52.5219&lon=13.4132" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/stations/8011160/walk?lat=52.5219&lon=13.4132", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.BT_SCHLUESSEL}`, },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/stations/8011160/walk?lat=52.5219&lon=13.4132",
    headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
    timeout=30,
)
daten = antwort.json()

QR-Code zur Bahnhofsseite

GET/v1/stations/{station_id}/qr.svgEimer standard

QR-Code (SVG, weiß auf transparent, runde Punkte wie auf den Anzeigen) mit dem Link zur Seite des Bahnhofs auf bahnhof.de. Gibt es dort keine Seite, führt er zur Startseite.

Pfad-Parameter

FeldTypBeschreibung
station_idPflichtstringEVA-Nummer

Antwort

image/svg+xml

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/qr.svg" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
const antwort = await fetch("https://developer.bahnhoftafel.de/v1/stations/8011160/qr.svg", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.BT_SCHLUESSEL}`, },
});
const daten = await antwort.json();
Python
import os, requests

antwort = requests.get(
    "https://developer.bahnhoftafel.de/v1/stations/8011160/qr.svg",
    headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
    timeout=30,
)
daten = antwort.json()