Deutsche Bahn Ticket Scraper
Pricing
from $1.50 / 1,000 trips
Deutsche Bahn Ticket Scraper
Scrape Deutsche Bahn connection options and ticket prices with this Actor. Stay on top of the data of Germany's largest railway company.
Pricing
from $1.50 / 1,000 trips
Rating
5.0
(1)
Developer
Jindřich Bär
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Deutsche Bahn train tickets & connections scraper
Search Deutsche Bahn train connections and ticket prices across Germany and the rest of Europe, and get back a structured dataset of routes, times, transfers, and fares — ready to plug into spreadsheets, databases, dashboards, or AI agents.
Pulls live data from the official Deutsche Bahn (DB / bahn.de) timetable so you always get the same trains, prices, and schedules a passenger would see in the DB Navigator app.
What you can do with it
- Compare ticket prices between stations in real time — Berlin to Munich, Hamburg to Frankfurt, Prague to Berlin, Paris to Cologne, and any other route the DB timetable covers (including cross-border ICE, EC, IC, EuroNight, and regional services).
- Find direct trains or limit results to a maximum number of transfers.
- Monitor fares over time by scheduling the actor to run daily / hourly and writing the results to your own datastore.
- Build a travel-planning assistant — feed the JSON output directly into an LLM agent that answers "what's the cheapest train from X to Y on date Z" or "is there a direct ICE from Berlin to Munich tomorrow morning."
- Track delays and journey duration — every leg includes scheduled times, forecast (real-time) times, and the delta in minutes.
Typical use cases: travel comparison sites, price-monitoring tools, business-trip planners, train spotters, journalists working on transport coverage, and AI agents that need a structured rail-data source for Germany and Europe.
Inputs
| Field | Required | Default | Description |
|---|---|---|---|
from | yes | — | Departure station (e.g. "Berlin Hbf", "Praha hl.n.", "Verona") |
to | yes | — | Destination station |
date | no | today | Departure date (date picker, ISO YYYY-MM-DD) |
time | no | now | Departure time (HH:MM, 24-hour) |
firstClass | no | false | Return 1st-class fares instead of 2nd-class |
bikeCarriage | no | false | Only return journeys where bicycle carriage is available |
maxTransfers | no | 10 | Maximum allowed transfers per journey. 0 returns direct connections only |
maxResults | no | 20 | Maximum number of connections to push to the dataset |
Station names are matched against the official DB station list — local accents, German umlauts, and foreign-station names are all supported. The actor automatically paginates through the DB timetable and walks forward across calendar days, so you reliably get the requested number of connections even on sparsely-served international routes.
Example input
{"from": "Berlin Hbf","to": "München Hbf","date": "2026-06-15","time": "08:00","firstClass": false,"maxTransfers": 2,"maxResults": 25}
Output
Each item in the dataset describes one train connection.
Example item
{"id": "fba8c42c_3","from": "Berlin Hbf","to": "München Hbf","fromId": "8011160","toId": "8000261","departure": "2026-06-15T08:34:00+02:00","arrival": "2026-06-15T12:58:00+02:00","durationMinutes": 264,"transfers": 0,"priceAmount": 89.99,"priceCurrency": "EUR","legs": [{"tripId": "2|#VN#1#ST#...","line": "ICE 599","direction": "München Hbf","from": "Berlin Hbf","to": "München Hbf","fromId": "8011160","toId": "8000261","departure": "2026-06-15T08:34:00+02:00","arrival": "2026-06-15T12:58:00+02:00","departureDelayMinutes": null,"arrivalDelayMinutes": null,"durationMinutes": 264,"walking": false}]}
Field reference
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier for the journey (deduplication-safe across runs) |
from / to | string | Station names |
fromId / toId | string | DB station IDs (HAFAS / EVA numbers) |
departure / arrival | ISO 8601 with timezone offset | Forecast time when available, else the scheduled time |
durationMinutes | integer | Total travel time |
transfers | integer | Number of transfers (0 = direct) |
priceAmount | number or null | Ticket price in priceCurrency. Null when ticketing is unavailable |
priceCurrency | string or null | ISO 4217 (typically "EUR") |
legs[] | array | Per-leg breakdown: line name (ICE, EC, IC, RE, …), direction, departure/arrival, delays, walking transfers |
priceAmount is null on journeys where DB doesn't sell a ticket (e.g. some cross-border legs,
specific operator-only fares). Walking legs between platforms or stations are included in legs[]
with walking: true.
Pricing
Pay-per-event — one search-result event is charged for each connection pushed to the dataset.
That means a query with maxResults: 10 charges for at most ten events, regardless of how many DB
calls the actor makes under the hood.
Using the API
Trigger runs from your own code via the Apify API. With your Apify API token, a POST request runs
the actor synchronously and returns the dataset items:
curl -X POST "https://api.apify.com/v2/acts/jindrich.bar~deutsche-bahn-ticket-scraper/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"from": "Berlin Hbf", "to": "München Hbf", "maxResults": 10}'
Or run asynchronously and poll for status / dataset items:
# Start a runcurl -X POST "https://api.apify.com/v2/acts/jindrich.bar~deutsche-bahn-ticket-scraper/runs?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"from": "Berlin Hbf", "to": "München Hbf"}'# When it's done, read the datasetcurl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<APIFY_TOKEN>"
Official client libraries are available for JavaScript / TypeScript, Python, and via the Apify REST API directly.
Scheduling
Run the actor on a cron schedule from the Schedules tab in the Apify console — daily, hourly, or any custom cron expression. Common patterns:
- Daily price snapshot at 09:00 — track fare changes for a fixed Berlin → Munich route and chart the trend over weeks.
- Hourly refresh for a launch week — keep a real-time price board for a route during a promotional campaign.
- Weekly market scan — compare a basket of intercity routes (
Berlin↔Hamburg,Munich↔Frankfurt,Cologne↔Stuttgart) for week-over-week price changes.
Schedules can fan out into multiple datasets, push to a webhook, or trigger downstream actors when the run finishes.
Use with AI Agents (Apify MCP)
This actor is exposed through the Apify Model Context Protocol (MCP) server, so any AI agent that speaks MCP — Claude, ChatGPT custom agents, OpenAI Agents SDK, Cursor, etc. — can call it directly to fetch live DB ticket prices and connection options.
Once the Apify MCP server is connected, the agent picks up the actor's input schema automatically. Typical prompts that work out of the box:
- "What's the cheapest train from Berlin to Munich tomorrow morning?"
- "Find me a direct ICE from Hamburg Hbf to Frankfurt am Main Hbf on 2026-06-15."
- "How long does the train from Berlin to Prague take, and when does the next one leave?"
- "List the next five trains from Cologne to Brussels under €80."
- "Are there any direct overnight trains from Berlin to Vienna in the next two weeks?"
The agent fills the input, runs the actor, and reads the structured dataset items back — no scraping, no HTML parsing, no scheduling logic on the agent side.
Why this works well for agents
- Typed input schema — every field has a title, type, default, and validation rules, so an agent can call the actor without trial-and-error prompting.
- Typed output —
departure,arrival,durationMinutes,transfers,priceAmountare all numbers / ISO timestamps, ready to be diffed, sorted, or compared directly. - Predictable pagination — results come back in chronological order with duplicates removed and gaps in service skipped, so the agent doesn't have to reason about paging.
- Pay-per-event cost control — an agent that asks for five connections pays for five.
Troubleshooting & support
Most issues come from date/time formatting or sparsely-served routes. Try the fixes below before opening an issue.
Common problems
No results returned (reason: "no-results")
- The departure date is in the past, or further than ~12 months in the future (DB's timetable horizon). Pick a date within the next year.
- The route genuinely has no service on the requested day (e.g. some regional cross-border lines
only run on certain weekdays). Try shifting the date or relaxing
maxTransfers.
priceAmount is null on some / all journeys
- DB doesn't sell a through-ticket for the connection. This is normal for some cross-border legs, operator-only fares, or night trains where tickets are sold by the partner operator.
- The journey includes a leg DB cannot ticket (e.g. ÖBB Nightjet on certain dates, some SNCF TGVs). The connection is still returned with full timing and leg details — only the price is missing.
- The forecast price wasn't yet available because the connection is more than 6 months out (DB typically opens advance fares ~180 days ahead).
Fewer results than maxResults
- On sparsely-served routes (small regional stations, late-night cross-border), the actor walks
forward day-by-day up to ~365 days to find connections. If it still can't reach
maxResults, the timetable simply doesn't have more matching trains. Try wideningmaxTransfersor starting from a larger nearby hub.
Invalid date or Invalid time error
datemust beYYYY-MM-DD(e.g.2026-06-15).timemust beHH:MMin 24-hour format (e.g.08:00, not8 AM). Both fields are optional — leave them empty to use "now".
Run is slower than expected
- Long international itineraries with many transfers can take 30-60s as the actor paginates through
the DB timetable. This is expected. Use a tighter
maxTransfers(e.g.2) and reasonablemaxResultsto keep runs fast and cheap.
Different prices than bahn.de shows in a browser
- The actor returns the cheapest available fare class (Super Sparpreis / Sparpreis / Flexpreis) for the requested journey. Promo codes, BahnCard discounts, and group fares aren't applied — those require a logged-in DB account.
- Set
firstClass: trueif you want 1st-class fares instead of the default 2nd-class.
Delays show as null
- Real-time delay forecasts are only published a few hours before departure. For journeys further
out,
departureDelayMinutesandarrivalDelayMinutesarenulluntil DB's live data kicks in.
FAQs
Can I search by station ID instead of name?
Not currently — from and to accept station names only. The actor resolves them to the official
HAFAS / EVA IDs internally and returns those as fromId / toId so you can round-trip them.
Does it cover S-Bahn, U-Bahn, trams, or buses? Long-distance and regional rail (ICE, EC, IC, EuroNight, IRE, RE, RB) plus operator partners visible in the DB timetable. Urban transit is out of scope. For long-distance buses, see the Flixbus actor below.
Does it work for routes that don't touch Germany? Yes — anything the DB timetable indexes is searchable, including Vienna–Zurich, Paris–Brussels, Amsterdam–Copenhagen, and similar. Coverage drops on purely-domestic routes in other countries; use the relevant national actor instead (e.g. České dráhy for Czech-only journeys).
Can I get seat availability or book a ticket? No — the actor returns timetable and fare data only. Booking requires DB's authenticated checkout flow.
Support
Open an issue on the actor's Issues tab in the Apify console. Include the full input JSON, the
run ID (visible in the run URL, e.g. console.apify.com/actors/runs/<RUN_ID>), and the expected
vs. actual output — that lets the maintainer pull the exact logs and reproduce the issue quickly.
Need a different data source?
If you're scraping connections across multiple operators, check our companion actors:
- České dráhy connection scraper — Czech rail timetable and ticket prices.
- Flixbus connection scraper — long-distance bus routes across Europe and the US.
All three actors emit a comparable schema (from, to, departure, arrival, price, leg-level
breakdown), so an aggregator agent can merge their outputs into a single multi-modal travel search.