Developer
Menü

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

FeldTypBeschreibung
typestringnew (neue Zugnummer), route_change (anderer Laufweg), cancelled (fährt laut Fahrplan nicht) oder ends (fährt ab from_date nicht mehr)
trainobject{ name, category, number }
from_datestringErster betroffener Betriebstag JJJJ-MM-TT
to_datestringLetzter betroffener Betriebstag
daysintegerZahl der betroffenen Tage (bei Lücken kleiner als der Zeitraum)
open_endedbooleantrue, wenn es bis zum Ende des abgefragten Zeitraums so bleibt
timetable_changebooleantrue, wenn es mit dem Fahrplanwechsel im Dezember beginnt
origin?objectStart { id, name } an diesen Tagen
destination?objectZiel { id, name }
usual_origin?objectNur bei route_change: der übliche Start
usual_destination?objectNur bei route_change: das übliche Ziel
departure?stringAbfahrt am Start am ersten Tag (ISO 8601)
arrival?stringAnkunft am Ziel am ersten Tag
shape?stringNur 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_atstringWann wir es zuerst bemerkt haben
cross_check?stringGegenprü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.
textstringEin 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

GET/v1/timetable/changesEimer standard

Alle bekannten Änderungen von heute an, sortiert nach dem ersten betroffenen Tag. Mit noticed_since nur, was seitdem neu bemerkt wurde (zum regelmäßigen Abholen).

Info

„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

FeldTypBeschreibung
typestring[]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
categorystringNur diese Gattung, z. B. ICE
fromstringNur, was an oder nach diesem Tag noch gilt
tostringNur, was bis zu diesem Tag beginnt
noticed_sinceISO 8601Nur, was wir seit diesem Zeitpunkt bemerkt haben
limitintegerHöchstens so vieleStandard: 2001 bis 1000

Antwort

Liste von Fahrplanänderung

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/timetable/changes" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
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();
Python
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

GET/v1/trains/{train}/scheduleEimer standard

Die 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

FeldTypBeschreibung
trainPflichtstringZug, z. B. ICE_1970, ICE 1970 oder nur 1970

Query-Parameter

FeldTypBeschreibung
daysintegerSo 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
curl "https://developer.bahnhoftafel.de/v1/trains/train/schedule" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
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();
Python
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

GET/v1/timetable/trainsEimer standard

Jeder 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

FeldTypBeschreibung
categorystringNur diese Gattung, z. B. ICE
stationstringNur Züge, die hier starten oder enden (EVA-Nummer)
limitintegerHöchstens so vieleStandard: 10001 bis 5000
offsetintegerSo viele überspringenStandard: 00 bis 100000

Antwort

Liste von { train, origin, destination, next_date, days }

Beispiel

curl
curl "https://developer.bahnhoftafel.de/v1/timetable/trains" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
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();
Python
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

GET/v1/timetable/statusEimer standard

Wie 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
curl "https://developer.bahnhoftafel.de/v1/timetable/status" \
  -H "Authorization: Bearer $BT_SCHLUESSEL"
JavaScript
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();
Python
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()