Search train connections between two stations with prices.
Finds Deutsche Bahn connections (ICE, IC, regional, S-Bahn, bus) like bahn.de's own search: departure and arrival times, duration, transfers, trains with platforms, and the cheapest fare ('ab' price) for the chosen class. Station names are free text; the first station match is used and returned. Runs only inside the user's browser: bahn.de's Akamai protection rejects every request without the browser's own cookies.
- Site
- Deutsche Bahn, Germany
- Version
- v1
- Runs, last 7 days
- No reports yet
- sha256
- 178e36c2502f…
Requests
sent from your browser, in order- 1GETwww.bahn.de/web/api/reiseloesung/orte?limit&suchbegriff&typFree text to HAFAS station id (type ST = station).
- 2GETwww.bahn.de/web/api/reiseloesung/orte?limit&suchbegriff&typ
- 3POSTwww.bahn.de/web/api/angebote/fahrplanConnection search with prices. Answers 201 on success. Minimal body from spikes/bahn.md; one adult, no discount card.
Parameters
from your agentadultsintegerNumber of adult travellers without discount card = 1
datestringrequiredTravel date YYYY-MM-DD
fromstringrequiredDeparture station, e.g. 'Berlin Hbf', 'Hamburg'
modeenumABFAHRT = depart at or after time, ANKUNFT = arrive by time "ABFAHRT" | "ANKUNFT" = "ABFAHRT"
page_refstringnext_page_ref from a previous result, to get later connections with otherwise identical params
timestringHH:mm, Europe/Berlin local time = "08:00"
tostringrequiredArrival station, e.g. 'München Hbf'
travel_classenum2nd or 1st class (changes the price) "KLASSE_2" | "KLASSE_1" = "KLASSE_2"
Returns
to your agentconnectionsobject[]arrival, arrival_realtime, currency, departure, departure_realtime, duration_min, from, legs, price_class, price_eur, to, trains, transfers
datestring
fromstringDeparture station actually used (first station match for params.from)
next_page_refstringPass as page_ref (with the same other params) to get later connections
tostringArrival station actually used
Where it can send
- www.bahn.de
Anything else is blocked by the extension.
Reads from your session
Nothing. Your browser attaches the site's cookies by itself.
Used only in requests to the addresses on the left. Never sent to wield.run or your agent.
Checks
a run passes when- length(output.connections) > `0`
- output.from == 'Berlin Hbf'
- output.to == 'München Hbf'
- starts_with(output.connections[0].departure, '2026-11-20')
- length(output.connections[?price_eur != `null`]) > `0`
- output.connections[0].duration_min > `200`
Raw recipe
Served as canonical JSON from /api/v1/recipes/bahn.de/search-connections, so its sha256 matches the ETag and the index.
Check it yourself
curl -s https://wield.run/api/v1/recipes/bahn.de/search-connections | shasum -a 256 # 178e36c2502fdb6e729c6b2219743834c06efe83cf1a716b9da414d8bee23c52
bahn.de/search-connections.json, v1
{ "description": "Finds Deutsche Bahn connections (ICE, IC, regional, S-Bahn, bus) like bahn.de's own search: departure and arrival times, duration, transfers, trains with platforms, and the cheapest fare ('ab' price) for the chosen class. Station names are free text; the first station match is used and returned. Runs only inside the user's browser: bahn.de's Akamai protection rejects every request without the browser's own cookies.", "effect": "read", "id": "bahn.de/search-connections", "keywords": [ "bahn", "deutsche bahn", "db", "zug", "zugverbindung", "fahrplan", "verbindung suchen", "ice", "fahrkarte", "ticketpreis", "train", "train connection", "timetable", "rail", "ticket price", "germany train" ], "output": { "schema": { "properties": { "connections": { "items": { "properties": { "arrival": { "type": "string" }, "arrival_realtime": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "departure": { "description": "Scheduled, Europe/Berlin local, YYYY-MM-DDTHH:mm:ss", "type": "string" }, "departure_realtime": { "description": "Real-time forecast, only close to departure", "type": [ "string", "null" ] }, "duration_min": { "type": [ "integer", "null" ] }, "from": { "type": "string" }, "legs": { "items": { "properties": { "arrival": { "type": "string" }, "arrival_platform": { "type": [ "string", "null" ] }, "category": { "type": "string" }, "departure": { "type": "string" }, "departure_platform": { "type": [ "string", "null" ] }, "direction": { "type": [ "string", "null" ] }, "from": { "type": "string" }, "to": { "type": "string" }, "train": { "type": "string" } }, "type": "object" }, "type": "array" }, "price_class": { "type": [ "string", "null" ] }, "price_eur": { "description": "Cheapest fare for this connection, class and travellers ('ab' price, Sparpreis if available). null when bahn.de shows no price", "type": [ "number", "null" ] }, "to": { "type": "string" }, "trains": { "description": "e.g. ['ICE 1005']", "items": { "type": "string" }, "type": "array" }, "transfers": { "type": "integer" } }, "type": "object" }, "type": "array" }, "date": { "format": "date", "type": "string" }, "from": { "description": "Departure station actually used (first station match for params.from)", "type": "string" }, "next_page_ref": { "description": "Pass as page_ref (with the same other params) to get later connections", "type": [ "string", "null" ] }, "to": { "description": "Arrival station actually used", "type": "string" } }, "required": [ "from", "to", "date", "connections" ], "type": "object" }, "value": "{from: steps.from_station.name, to: steps.to_station.name, date: params.date, connections: steps.search.connections, next_page_ref: steps.search.later_ref}" }, "params": { "additionalProperties": false, "properties": { "adults": { "default": 1, "description": "Number of adult travellers without discount card", "maximum": 9, "minimum": 1, "type": "integer" }, "date": { "description": "Travel date YYYY-MM-DD", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "type": "string" }, "from": { "description": "Departure station, e.g. 'Berlin Hbf', 'Hamburg'", "maxLength": 80, "minLength": 2, "type": "string" }, "mode": { "default": "ABFAHRT", "description": "ABFAHRT = depart at or after time, ANKUNFT = arrive by time", "enum": [ "ABFAHRT", "ANKUNFT" ], "type": "string" }, "page_ref": { "description": "next_page_ref from a previous result, to get later connections with otherwise identical params", "maxLength": 2000, "type": "string" }, "time": { "default": "08:00", "description": "HH:mm, Europe/Berlin local time", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", "type": "string" }, "to": { "description": "Arrival station, e.g. 'München Hbf'", "maxLength": 80, "minLength": 2, "type": "string" }, "travel_class": { "default": "KLASSE_2", "description": "2nd or 1st class (changes the price)", "enum": [ "KLASSE_2", "KLASSE_1" ], "type": "string" } }, "required": [ "from", "to", "date" ], "type": "object" }, "provenance": { "method": "browser-inspect", "notes": "spikes/bahn.md. Akamai Bot Manager: every /web/api call without the browser's own _abck/bm_sz cookies gets 403 OPS_BLOCKED (curl, credentials:'omit'). Automated browsers (Playwright, Claude browser pane) also get 403 on fahrplan, so this recipe can only be verified in a human-driven Chrome with the extension (tests/e2e/live). Run it in a www.bahn.de tab that has fully loaded. Content-Type is required (415 without). Leg time fields: the web API uses abfahrtsZeitpunkt/ankunftsZeitpunkt (+ ez* for real time) per db-vendo-client's dbweb parser; the spike recorded abfahrt.sollzeit, so both are read. Discount cards (BahnCard) and children are not mapped yet.", "recorded_at": "2026-11-07T11:20:00+01:00", "recorded_by": "C4" }, "requires_login": false, "schema": "https://wield.run/schema/recipe/v1.json", "site": { "country": "DE", "id": "bahn.de", "language": "de", "login_url": "https://www.bahn.de/buchung/login", "name": "Deutsche Bahn", "origin": "https://www.bahn.de", "origins": [ "https://www.bahn.de" ] }, "steps": [ { "description": "Free text to HAFAS station id (type ST = station).", "expect": { "assert": "length(body[?type=='ST']) > `0`", "content_type": "json", "status": [ 200 ] }, "extract": { "json": { "eva": "[?type=='ST'] | [0].extId", "id": "[?type=='ST'] | [0].id", "name": "[?type=='ST'] | [0].name" } }, "id": "from_station", "request": { "headers": { "Accept": "application/json", "Accept-Language": "de" }, "method": "GET", "query": { "limit": "10", "suchbegriff": "{{params.from}}", "typ": "ALL" }, "url": "https://www.bahn.de/web/api/reiseloesung/orte" } }, { "expect": { "assert": "length(body[?type=='ST']) > `0`", "content_type": "json", "status": [ 200 ] }, "extract": { "json": { "eva": "[?type=='ST'] | [0].extId", "id": "[?type=='ST'] | [0].id", "name": "[?type=='ST'] | [0].name" } }, "id": "to_station", "request": { "headers": { "Accept": "application/json", "Accept-Language": "de" }, "method": "GET", "query": { "limit": "10", "suchbegriff": "{{params.to}}", "typ": "ALL" }, "url": "https://www.bahn.de/web/api/reiseloesung/orte" } }, { "delay_ms": 300, "description": "Connection search with prices. Answers 201 on success. Minimal body from spikes/bahn.md; one adult, no discount card.", "expect": { "assert": "body.verbindungen != null", "content_type": "json", "status": [ 200, 201 ] }, "extract": { "json": { "connections": "verbindungen[].{departure: verbindungsAbschnitte[0].abfahrtsZeitpunkt || verbindungsAbschnitte[0].abfahrt.sollzeit, departure_realtime: verbindungsAbschnitte[0].ezAbfahrtsZeitpunkt || verbindungsAbschnitte[0].abfahrt.echtzeit || `null`, arrival: verbindungsAbschnitte[-1].ankunftsZeitpunkt || verbindungsAbschnitte[-1].ankunft.sollzeit, arrival_realtime: verbindungsAbschnitte[-1].ezAnkunftsZeitpunkt || verbindungsAbschnitte[-1].ankunft.echtzeit || `null`, from: verbindungsAbschnitte[0].abfahrtsOrt, to: verbindungsAbschnitte[-1].ankunftsOrt, duration_min: verbindungsDauerInSeconds && floor(verbindungsDauerInSeconds / `60`) || `null`, transfers: umstiegsAnzahl, trains: verbindungsAbschnitte[?verkehrsmittel.typ=='PUBLICTRANSPORT'].verkehrsmittel.mittelText, price_eur: angebotsPreis.betrag || `null`, currency: angebotsPreis.waehrung || `null`, price_class: angebotsPreisKlasse || `null`, legs: verbindungsAbschnitte[?verkehrsmittel.typ=='PUBLICTRANSPORT'].{train: verkehrsmittel.mittelText || verkehrsmittel.name, category: verkehrsmittel.produktGattung, direction: verkehrsmittel.richtung || `null`, from: abfahrtsOrt, to: ankunftsOrt, departure: abfahrtsZeitpunkt || abfahrt.sollzeit, arrival: ankunftsZeitpunkt || ankunft.sollzeit, departure_platform: halte[0].ezGleis || halte[0].gleis || `null`, arrival_platform: halte[-1].ezGleis || halte[-1].gleis || `null`}}", "later_ref": "verbindungReference.later" } }, "id": "search", "request": { "body": { "type": "json", "value": { "abfahrtsHalt": "{{steps.from_station.id}}", "anfrageZeitpunkt": "{{params.date}}T{{params.time}}:00", "ankunftSuche": "{{params.mode}}", "ankunftsHalt": "{{steps.to_station.id}}", "deutschlandTicketVorhanden": false, "klasse": "{{params.travel_class}}", "nurDeutschlandTicketVerbindungen": false, "pagingReference": "{{params.page_ref}}", "produktgattungen": [ "ICE", "EC_IC", "IR", "REGIONAL", "SBAHN", "BUS", "SCHIFF", "UBAHN", "TRAM", "ANRUFPFLICHTIG" ], "reisende": [ { "alter": [], "anzahl": "{{params.adults}}", "ermaessigungen": [ { "art": "KEINE_ERMAESSIGUNG", "klasse": "KLASSENLOS" } ], "typ": "ERWACHSENER" } ], "schnelleVerbindungen": true } }, "headers": { "Accept": "application/json", "Accept-Language": "de", "Content-Type": "application/json; charset=utf-8" }, "method": "POST", "url": "https://www.bahn.de/web/api/angebote/fahrplan" } } ], "task": "Search train connections between two stations with prices", "tos": { "notes": "bahn.de's terms do not allow automated bulk queries of timetable or fare data. This recipe performs one user-initiated search at a time from the user's own browser and session, at human pace (3 requests). Do not poll it or page through results in bulk. It never books: booking and payment stay with the user on bahn.de.", "reviewed": false }, "verify": { "checks": [ "length(output.connections) > `0`", "output.from == 'Berlin Hbf'", "output.to == 'München Hbf'", "starts_with(output.connections[0].departure, '2026-11-20')", "length(output.connections[?price_eur != `null`]) > `0`", "output.connections[0].duration_min > `200`" ], "example_params": { "date": "2026-11-20", "from": "Berlin Hbf", "time": "08:00", "to": "München Hbf" }, "max_ms": 8000 }, "version": 1 }