Fahrplan
Was sich im Fernverkehr ändert, bevor es passiert: neue Zugnummern (Entlastungs-, Ersatz- und Sonderzüge, neue Züge im nächsten Fahrplan), Züge mit anderem Laufweg als sonst, Fahrten, die aus dem Fahrplan verschwinden, und Züge, die ab einem Tag nicht mehr fahren. Wir schauen dafür laufend auf die Abfahrtstafeln der großen Knoten, für die nahen Tage besonders oft, und weit voraus bis über den Fahrplanwechsel im Dezember. Nach und nach kommen auch alle anderen Bahnhöfe mit Fernverkehr dran, und jede Änderung prüfen wir bei bahn.de gegen.
Objekte
Objekt Fahrplanänderung
| Feld | Typ | Beschreibung |
|---|---|---|
type | string | new (neue Zugnummer), route_change (anderer Laufweg), cancelled (fährt laut Fahrplan nicht) oder ends (fährt ab from_date nicht mehr) |
train | object | { name, category, number } |
from_date | string | Erster betroffener Betriebstag JJJJ-MM-TT |
to_date | string | Letzter betroffener Betriebstag |
days | integer | Zahl der betroffenen Tage (bei Lücken kleiner als der Zeitraum) |
open_ended | boolean | true, wenn es bis zum Ende des abgefragten Zeitraums so bleibt |
timetable_change | boolean | true, wenn es mit dem Fahrplanwechsel im Dezember beginnt |
origin | ?object | Start { id, name } an diesen Tagen |
destination | ?object | Ziel { id, name } |
usual_origin | ?object | Nur bei route_change: der übliche Start |
usual_destination | ?object | Nur bei route_change: das übliche Ziel |
departure | ?string | Abfahrt am Start am ersten Tag (ISO 8601) |
arrival | ?string | Ankunft am Ziel am ersten Tag |
shape | ?string | Nur bei route_change, sobald die Halte bekannt sind: shortened (Teilstrecke), extended (fährt weiter), different (kaum gemeinsame Halte) oder changed |
route | ?object[] | Halte { id, name } am ersten Tag (bei new und route_change, sobald nachgeladen) |
usual_route | ?object[] | Nur bei route_change: Halte auf dem üblichen Laufweg |
noticed_at | string | Wann wir es zuerst bemerkt haben |
cross_check | ?string | Gegenprüfung mit den Daten der DB (RIS-Abfahrtstafel): confirmed (die DB kennt es genauso), not_listed (am Startbahnhof zu der Zeit nicht gelistet) oder null (noch nicht geprüft). Was die DB anders kennt, melden wir gar nicht. |
text | string | Ein Satz auf Deutsch, z. B. „Vom 19. bis 21. Oktober fährt ICE 1970 von Köln Hbf nach Hannover Hbf statt von Dresden Hbf nach Berlin Hbf.“ |
{
"type": "route_change",
"train": {
"name": "ICE 1970",
"category": "ICE",
"number": "1970"
},
"from_date": "2026-10-19",
"to_date": "2026-10-21",
"days": 3,
"open_ended": false,
"timetable_change": false,
"origin": {
"id": "8000207",
"name": "Köln Hbf"
},
"destination": {
"id": "8000152",
"name": "Hannover Hbf"
},
"usual_origin": {
"id": "8010085",
"name": "Dresden Hbf"
},
"usual_destination": {
"id": "8011160",
"name": "Berlin Hbf"
},
"departure": "2026-10-19T09:00:00+02:00",
"arrival": "2026-10-19T11:40:00+02:00",
"shape": "different",
"route": [
{
"id": "8000207",
"name": "Köln Hbf"
},
{
"id": "8000080",
"name": "Dortmund Hbf"
},
{
"id": "8000152",
"name": "Hannover Hbf"
}
],
"usual_route": [
{
"id": "8010085",
"name": "Dresden Hbf"
},
{
"id": "8010205",
"name": "Leipzig Hbf"
},
{
"id": "8011160",
"name": "Berlin Hbf"
}
],
"noticed_at": "2026-10-16T08:12:41.000Z",
"cross_check": "confirmed",
"text": "Vom 19. bis 21. Oktober fährt ICE 1970 von Köln Hbf nach Hannover Hbf statt von Dresden Hbf nach Berlin Hbf."
}Fahrplanänderungen
/v1/timetable/changesEimer standardAlle bekannten Änderungen von heute an, sortiert nach dem ersten betroffenen Tag. Mit noticed_since nur, was seitdem neu bemerkt wurde (zum regelmäßigen Abholen).
„Neu“ melden wir erst, wenn der Zug in den Tagen davor nachweislich nicht fuhr. Züge, die nur am Wochenende fahren, gelten so nicht jede Woche als neu.
Query-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
type | string[] | Nur diese Arten, mit Komma getrenntnew Neue Zugnummer im Fahrplan, route_change Anderer Laufweg als sonst, cancelled Fährt laut Fahrplan nicht (aus dem Fahrplan verschwunden), ends Fährt ab einem Tag nicht mehr |
category | string | Nur diese Gattung, z. B. ICE |
from | string | Nur, was an oder nach diesem Tag noch gilt |
to | string | Nur, was bis zu diesem Tag beginnt |
noticed_since | ISO 8601 | Nur, was wir seit diesem Zeitpunkt bemerkt haben |
limit | integer | Höchstens so vieleStandard: 2001 bis 1000 |
Antwort
Liste von Fahrplanänderung
Beispiel
curl "https://developer.bahnhoftafel.de/v1/timetable/changes" \
-H "Authorization: Bearer $BT_SCHLUESSEL"const antwort = await fetch("https://developer.bahnhoftafel.de/v1/timetable/changes", {
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/timetable/changes",
headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
timeout=30,
)
daten = antwort.json()Plan eines Zuges
/v1/trains/{train}/scheduleEimer standardDie nächsten Wochen eines Zuges Tag für Tag: ob er fährt, von wo nach wo, wann, und ob das anders ist als sonst. Dazu seine Änderungen. Kennt auch Züge, die noch nie gefahren sind. Fragst du nach einem Zug, den wir gar nicht kennen, suchen wir gezielt danach (Antwort 404 train_searching) und schauen danach noch eine ganze Weile regelmäßig nach.
Pfad-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
trainPflicht | string | Zug, z. B. ICE_1970, ICE 1970 oder nur 1970 |
Query-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
days | integer | So viele Tage ab heuteStandard: 421 bis 120 |
Antwort
Objekt { train, days, changes }; days je Fahrt { date, origin, destination, departure, arrival, state, operator, confirmed_at } mit state regular, route_change oder cancelled; Tage ohne Fahrt fehlen. changes: Liste von Fahrplanänderung.
Beispiel
curl "https://developer.bahnhoftafel.de/v1/trains/train/schedule" \
-H "Authorization: Bearer $BT_SCHLUESSEL"const antwort = await fetch("https://developer.bahnhoftafel.de/v1/trains/train/schedule", {
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/trains/train/schedule",
headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
timeout=30,
)
daten = antwort.json()Alle Züge im Fahrplan
/v1/timetable/trainsEimer standardJeder Fernzug, der in den nächsten Wochen fährt, mit seinem häufigsten Laufweg, dem nächsten Tag und der Zahl der Tage.
Query-Parameter
| Feld | Typ | Beschreibung |
|---|---|---|
category | string | Nur diese Gattung, z. B. ICE |
station | string | Nur Züge, die hier starten oder enden (EVA-Nummer) |
limit | integer | Höchstens so vieleStandard: 10001 bis 5000 |
offset | integer | So viele überspringenStandard: 00 bis 100000 |
Antwort
Liste von { train, origin, destination, next_date, days }
Beispiel
curl "https://developer.bahnhoftafel.de/v1/timetable/trains" \
-H "Authorization: Bearer $BT_SCHLUESSEL"const antwort = await fetch("https://developer.bahnhoftafel.de/v1/timetable/trains", {
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/timetable/trains",
headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
timeout=30,
)
daten = antwort.json()Stand des Fahrplan-Spähers
/v1/timetable/statusEimer standardWie weit der Späher ist: Knoten, abgefragte Tafeln, Zeitraum, Abdeckung der Züge, Rundgang, Gegenprüfung bei der DB, gezielte Suchen, Zahl der bekannten Fahrten und Änderungen.
Antwort
Objekt { hubs, hubs_learned, days, horizon, timetable_change, boards_total, boards_never, boards_due, baseline_complete, runs, trains, coverage, changes, boards_fetched, errors, archive_queued, last_board, hub_list }. coverage: { trains, at_two_hubs, at_one_hub, not_seen } für die nächsten sieben Tage; round_trip: Rundgang über alle Bahnhöfe mit Fernverkehr { stations, boards }; cross_check: Gegenprüfung bei bahn.de { source, queued, results }; searches: gezielte Suchen { queued, requests, watched, found }; hub_list je Knoten { eva, name, reason, complete, boards, last } mit reason base (Grundliste) oder coverage (selbst dazugenommen).
Beispiel
curl "https://developer.bahnhoftafel.de/v1/timetable/status" \
-H "Authorization: Bearer $BT_SCHLUESSEL"const antwort = await fetch("https://developer.bahnhoftafel.de/v1/timetable/status", {
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/timetable/status",
headers={"Authorization": f"Bearer {os.environ['BT_SCHLUESSEL']}"},
timeout=30,
)
daten = antwort.json()