Deutsche Bahn Ticket Scraper avatar

Deutsche Bahn Ticket Scraper

Pricing

from $1.50 / 1,000 trips

Go to Apify Store
Deutsche Bahn Ticket Scraper

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

Jindřich Bär

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

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

FieldRequiredDefaultDescription
fromyesDeparture station (e.g. "Berlin Hbf", "Praha hl.n.", "Verona")
toyesDestination station
datenotodayDeparture date (date picker, ISO YYYY-MM-DD)
timenonowDeparture time (HH:MM, 24-hour)
firstClassnofalseReturn 1st-class fares instead of 2nd-class
bikeCarriagenofalseOnly return journeys where bicycle carriage is available
maxTransfersno10Maximum allowed transfers per journey. 0 returns direct connections only
maxResultsno20Maximum 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

FieldTypeDescription
idstringStable identifier for the journey (deduplication-safe across runs)
from / tostringStation names
fromId / toIdstringDB station IDs (HAFAS / EVA numbers)
departure / arrivalISO 8601 with timezone offsetForecast time when available, else the scheduled time
durationMinutesintegerTotal travel time
transfersintegerNumber of transfers (0 = direct)
priceAmountnumber or nullTicket price in priceCurrency. Null when ticketing is unavailable
priceCurrencystring or nullISO 4217 (typically "EUR")
legs[]arrayPer-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 run
curl -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 dataset
curl "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 outputdeparture, arrival, durationMinutes, transfers, priceAmount are 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 widening maxTransfers or starting from a larger nearby hub.

Invalid date or Invalid time error

  • date must be YYYY-MM-DD (e.g. 2026-06-15). time must be HH:MM in 24-hour format (e.g. 08:00, not 8 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 reasonable maxResults to 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: true if 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, departureDelayMinutes and arrivalDelayMinutes are null until 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:

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.