Python clients for the Deutsche Bahn APIs (Timetables, StaDa Station Data)
Project description
deutsche-bahn
A Python library for the Deutsche Bahn APIs — currently supporting the Timetables API and the StaDa Station Data API.
Requirements
- Python 3.13+
requests
Installation
uv add deutsche-bahn-py
Or with pip:
pip install deutsche-bahn-py
Setup
- Register at developers.deutschebahn.com, create an application, and subscribe to the APIs you need (Timetables and/or StaDa).
- Copy your Client ID and API Key.
Timetables API
from deutsche_bahn.timetables import TimetablesClient
client = TimetablesClient(
client_id="your_client_id",
api_key="your_api_key",
)
Find a station
stations = client.get_station("Frankfurt")
# [Station(name='Frankfurt am Main - Stadion', eva='8002040'), ...]
eva = stations[0].eva
Stations are identified by their EVA number, a numeric DB station ID.
Planned timetable
plan = client.get_plan(eva) # current hour
plan = client.get_plan(eva, hour=14) # specific hour today
plan = client.get_plan(eva, date=datetime(2026, 5, 20), hour=9)
for stop in plan.stops:
dp = stop.departure
print(stop.train_line.display_name, dp.planned_time, dp.planned_platform)
Returns one hour of scheduled departures/arrivals. No live data.
Live timetable (plan + changes merged)
live = client.get_timetable_with_changes(eva)
for stop in live.stops:
dp = stop.departure
if dp and dp.delay_minutes:
print(f"{stop.train_line.display_name} is +{dp.delay_minutes} min late")
if stop.is_cancelled:
print(f"{stop.train_line.display_name} is cancelled")
This is the most useful method — it fetches the plan and all current deviations, then merges them so each stop reflects reality.
Changes only
# All current deviations for the station (delays, cancellations, platform changes)
changes = client.get_full_changes(eva)
# Only what changed since your last call - use this when polling
recent = client.get_recent_changes(eva)
Data model
TimetableStop
| Attribute | Type | Description |
|---|---|---|
id |
str |
Unique stop identifier |
train_line |
TrainLine |
Train category and number |
arrival |
ArrivalDeparture | None |
Arrival data |
departure |
ArrivalDeparture | None |
Departure data |
messages |
list[Message] |
Delay/disruption messages |
is_cancelled |
bool |
True if arrival or departure is cancelled |
ArrivalDeparture
| Attribute | Type | Description |
|---|---|---|
planned_time |
datetime | None |
Scheduled time |
changed_time |
datetime | None |
Actual/expected time (set when delayed) |
planned_platform |
str |
Scheduled platform |
changed_platform |
str |
Actual platform (set when changed) |
planned_path |
list[str] |
Scheduled route as station names |
changed_path |
list[str] |
Actual route (set when rerouted) |
effective_time |
datetime | None |
changed_time if set, else planned_time |
effective_platform |
str |
changed_platform if set, else planned_platform |
delay_minutes |
int | None |
Difference in minutes, or None if on time |
is_cancelled |
bool |
True if status is cancelled |
TrainLine
| Attribute | Type | Description |
|---|---|---|
category |
str |
Train category (ICE, IC, RE, S, …) |
number |
str |
Train number |
owner |
str |
Operator code |
display_name |
str |
"{category} {number}" (e.g. "ICE 9551") |
Station
| Attribute | Type | Description |
|---|---|---|
eva |
str |
EVA station number |
name |
str |
Station name |
ds100 |
str |
DS100 abbreviation |
lat / lon |
float |
Coordinates |
StaDa Station Data API
from deutsche_bahn.stada import StaDaClient
client = StaDaClient(
client_id="your_client_id",
api_key="your_api_key",
)
Search for stations
results = client.get_stations(searchstring="Frankfurt")
# StationQuery(total=12, offset=0, limit=100, result=[...])
for station in results.result:
print(station.number, station.name, station.category)
Supports wildcards * and ? in the search string.
Filter options
# By federal state
results = client.get_stations(federalstate="bayern")
# By category range or list
results = client.get_stations(category="1-3")
results = client.get_stations(category="1,3,5")
# By EVA number
results = client.get_stations(eva=8000105)
# By Ril100 identifier
results = client.get_stations(ril="FF")
# Combine filters with OR logic
results = client.get_stations(searchstring="Hbf", federalstate="hessen", logicaloperator="or")
# Pagination
results = client.get_stations(offset=100, limit=50)
Fetch a single station
station = client.get_station(1071) # Bahnhofsnummer
print(station.result[0].name) # Frankfurt(Main)Hbf
3-S-Zentralen
3-S-Zentralen are the 24/7 operations centres for German railway stations.
# All 3-S-Zentralen
szentralen = client.get_szentralen()
# Single entry by ID
sz = client.get_szentrale(50)
print(sz.result[0].name, sz.result[0].public_phone_number)
Data model
StadaStation
| Attribute | Type | Description |
|---|---|---|
number |
int |
Bahnhofsnummer (station ID) |
name |
str |
Station name |
category |
int |
Station category (1–7) |
price_category |
int |
Price category |
federal_state |
str |
Federal state name |
has_wifi |
bool |
DB WiFi available |
has_db_lounge |
bool |
DB Lounge available |
has_travel_center |
bool |
DB Reisezentrum available |
has_locker_system |
bool |
Lockers available |
has_parking |
bool |
Parking available |
has_bicycle_parking |
bool |
Bicycle parking available |
has_taxi_rank |
bool |
Taxi rank available |
has_stepless_access |
str |
"yes", "no", or "partial" |
eva_numbers |
list[EVANumber] |
Associated EVA numbers |
ril100_identifiers |
list[RiL100Identifier] |
Ril100 codes |
mailing_address |
Address | None |
Station mailing address |
szentrale |
SZentrale | None |
Responsible 3-S-Zentrale |
SZentrale
| Attribute | Type | Description |
|---|---|---|
number |
int |
Unique ID |
name |
str |
Name |
public_phone_number |
str |
Public phone |
email |
str |
Email address |
address |
Address | None |
Physical address |
Exceptions
All clients share the same exception hierarchy:
| Exception | When |
|---|---|
AuthenticationError |
Invalid or missing credentials (401/403) |
NotFoundError |
Station or resource not found (404) |
RateLimitError |
Too many requests (429) |
DBApiError |
Any other API or network error |
from deutsche_bahn.stada.exceptions import NotFoundError, RateLimitError, DBApiError
try:
station = client.get_station(9999999)
except NotFoundError:
print("Station not found")
except RateLimitError:
print("Rate limit hit")
except DBApiError as e:
print(f"API error: {e}")
API reference
Data is provided under CC BY 4.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deutsche_bahn_py-0.2.2.tar.gz.
File metadata
- Download URL: deutsche_bahn_py-0.2.2.tar.gz
- Upload date:
- Size: 32.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0796ad70b9db0c5b2f42c650cbc1d7fd438b68a1132c5acbf69d49f9d966cd55
|
|
| MD5 |
b59cd0672eea3807a3a464ce558e3755
|
|
| BLAKE2b-256 |
6fb9c471380f62a0c0236676b2aef4e59f97c5d291ac1053505e239098535f2f
|
File details
Details for the file deutsche_bahn_py-0.2.2-py3-none-any.whl.
File metadata
- Download URL: deutsche_bahn_py-0.2.2-py3-none-any.whl
- Upload date:
- Size: 17.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7e598d5611c8b79ffd21689692eab2de0ffc3c0333d182bc0f9708322ad1d57
|
|
| MD5 |
8011ad70ac0b9450fe3e3590bb309e8b
|
|
| BLAKE2b-256 |
3ab09fca2722b75769dfc5535f9d44278f6ef2f2d6665e619fad1cec05941794
|