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
| Feld | Typ | Beschreibung |
|---|---|---|
station | object | { id, name }, bei Berlin der Bahnhof der S-Bahn |
platform | ?string | Gleis |
style | string | sbahn_berlin (S-Bahn Berlin mit Fahrzeugdaten) oder db (DB-Anzeiger mit Hinweisen und Wagenreihung) |
generated_at | string | Stand der Daten |
station_page | ?string | Seite des Bahnhofs auf bahnhof.de |
service | object | { suspended, replacement_service, ticker }: Verkehr eingestellt, Ersatzverkehr, Texte im Laufband |
trains | Zug 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
| Feld | Typ | Beschreibung |
|---|---|---|
trip_id | ?string | Für Fahrt abrufen, bei style=sbahn_berlin |
train | object | { name, line, number, product, color }, color ist { background, text, shape } wie das Linienzeichen |
destination | ?string | Ziel; null, wenn der Zug hier endet |
origin | ?string | Herkunft, nur wenn der Zug hier endet |
via | string[] | Halte, wie sie auf dem Anzeiger stehen |
via_announced | string[] | Halte, die in der Ansage genannt werden (Umsteigeknoten) |
route | string[] | Alle folgenden Halte (DB-Anzeiger) |
departure | ?object | { scheduled, expected, delay, prognosis } |
arrival | ?object | { scheduled, expected, estimated } |
platform | object | { scheduled, actual, changed } |
cancelled | boolean | Zug fällt aus |
additional | boolean | Zusätzlicher Zug |
starts_here | boolean | Zug beginnt hier |
ends_here | boolean | Zug 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 | ?string | Auslastung (low, medium, high) |
notices | object[] | Hinweise im Laufband { text, symbol }, z. B. Ersatzzug, Halt entfällt, Fahrradmitnahme |
split | ?object | Zugteilung { at, parts: [{ train, destination }] } |
bicycle_reservation | boolean | Fahrradmitnahme reservierungspflichtig |
previous_arrival | ?object | Der Zug kommt als { time, origin } hier an und fährt dann weiter |
coach_sequence | ?object | Wagenreihung in Prozent der Bahnsteiglänge: { direction, sectors, coaches, parts } |
realtime_source | ?string | gtfsrt (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
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Kennung; dieselbe Ansage kommt nie zweimal |
kind | string | einfahrt, eingefahren, endet, bereit, verspaetung, entfall, gleiswechsel, zusatzzug, verkehrt, stoerung, anschluss, information, hinweis … |
priority | ?integer | Vorrang, 100 = Einfahrt, 10 = allgemeiner Hinweis |
text | ?string | Der gesprochene Wortlaut |
trip_id | ?string | Fahrt, um die es geht |
spoken_at | string | Wann die Ansage fällig wurde |
valid_until | ?string | Bis wann sie noch zutrifft |
language | string | de |
audio_url | ?string | Tonaufnahme (MP3), eine Zeit lang abspielbar, ohne Schlüssel |
followups | object[] | Folgeansagen in anderen Sprachen { language, audio_url } |
speaker | string | Sprecherin der Aufnahmen: Alisa Palmer |
usage | string | private: 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
/v1/stations/{station_id}/platforms/{platform}Eimer standardWas 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 …“.
Die Daten sind sehr frisch. Für laufende Anzeigen ist Bahnsteig live besser.
Pfad-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
station_idPflicht | string | EVA-Nummer |
platformPflicht | string | Gleis, z. B. 3 oder 11a |
Antwort
{
"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 "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3" \
-H "Authorization: Bearer $BT_SCHLUESSEL"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();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
/v1/stations/{station_id}/platforms/{platform}/liveEimer liveServer-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.
Zählt zu den 2 gleichzeitigen Live-Strömen je App.
Pfad-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
station_idPflicht | string | EVA-Nummer |
platformPflicht | string | Gleis, 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 "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/live" \
-H "Authorization: Bearer $BT_SCHLUESSEL"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();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
/v1/stations/{station_id}/platforms/{platform}/announcementsEimer liveServer-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.
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.
Ansagen gibt es an S-Bahn-Gleisen in Berlin. Jede Ansage kommt genau einmal; wer sie verpasst, bekommt sie nicht nachgeliefert.
Pfad-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
station_idPflicht | string | EVA-Nummer |
platformPflicht | string | Gleis, 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 "https://developer.bahnhoftafel.de/v1/stations/8011160/platforms/3/announcements" \
-H "Authorization: Bearer $BT_ANSAGE_SCHLUESSEL"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();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
/v1/announcements/audioohne SchlüsselDie 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.
Ansagesprecherin: Alisa Palmer. Die Aufnahmen sind nur für den privaten Gebrauch zulässig: nicht weiterverbreiten, nicht in Videos, Streams oder Produkten verwenden.
Eigener Eimer ton: 20 Aufnahmen pro Minute je Adresse. Abgelaufene oder veränderte Links: 403 forbidden.
Query-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
qPflicht | string | aus audio_url |
expPflicht | integer | aus audio_url0 bis 99999999999 |
sigPflicht | string | aus audio_url |
Antwort
audio/mpeg
Beispiel
curl "https://developer.bahnhoftafel.de/v1/announcements/audio?q=wert&exp=wert&sig=wert"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();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
/v1/stations/{station_id}/walkEimer standardWann 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.
Startpunkte über 15 km Luftlinie werden abgelehnt. Die Gehzeit merken wir uns eine Weile; der Startpunkt wird dafür leicht gerundet.
Pfad-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
station_idPflicht | string | EVA-Nummer |
Query-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
latPflicht | number | Breite des Startpunkts47 bis 56 |
lonPflicht | number | Länge des Startpunkts5 bis 16 |
platform | string[] | Nur diese Gleise |
products | string[] | Nur diese Produktelong_distance Fernverkehr, regional Regionalverkehr, suburban S-Bahn |
buffer | integer | Puffer in Minuten (Fahrkarte, Treppe)Standard: 20 bis 15 |
announcement | bool | Ansagetext 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 "https://developer.bahnhoftafel.de/v1/stations/8011160/walk?lat=52.5219&lon=13.4132" \
-H "Authorization: Bearer $BT_SCHLUESSEL"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();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
/v1/stations/{station_id}/qr.svgEimer standardQR-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
| Feld | Typ | Beschreibung |
|---|---|---|
station_idPflicht | string | EVA-Nummer |
Antwort
image/svg+xml
Beispiel
curl "https://developer.bahnhoftafel.de/v1/stations/8011160/qr.svg" \
-H "Authorization: Bearer $BT_SCHLUESSEL"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();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()