Train Connection API Research (Europe)
Train Connection API Research (Europe) Status: Research complete, pending Omio affiliate application Date: 2026-04-30 Updated: 2026-07-29 — DB Navigator host...
Train Connection API Research (Europe)
Status: Research complete, pending Omio affiliate application Date: 2026-04-30 Updated: 2026-07-29 — DB Navigator host refreshed, DB/Flix train providers live, Transitous endpoint notes refreshed Related:
backend/apps/travel/,backend/apps/travel/providers/transitous_provider.py
Goal
Find an API that returns full European train connection details (times, operator, price, duration, changes) with a booking link per connection to an external provider. We do NOT want to be the ticket seller — that brings refund liability, consumer protection obligations, VAT handling, etc.
Current State
| Provider | What it does | Status |
|---|---|---|
| SerpAPI (Google Flights) | Flight search with prices + booking links | Production |
| Deutsche Bahn Navigator | German and selected cross-border train search with live DB prices + bahn.de deep links | Production |
| Flix | FlixBus / FlixTrain search with prices + booking links | Production |
| Transitous / MOTIS | European multimodal routes & timetables, no useful live prices/booking in sampled responses | Stub (transitous_provider.py) |
| Duffel | Flights only | Test file exists |
The search_connections skill supports transport_methods: ["train"] through Deutsche Bahn and Flix. TransitousProvider remains a stub for future timetable/multimodal enrichment.
Requirements
- Returns full connection details per result (departure/arrival times, duration, changes, operator/carrier, price)
- Returns a booking link per connection to an external booking provider
- We are an affiliate/comparison layer, NOT the ticket seller
- Accessible to individual developers (not enterprise-only)
- European coverage (domestic + cross-border)
APIs Evaluated
Omio Affiliate Search API — Recommended
Model: Affiliate — we search via their API, get connection details + deeplink per result, user clicks through to Omio to book. Commission per redirect/booking. We are NOT the seller.
Response fields per connection (from their OpenAPI spec):
departureLocation,arrivalLocationdepartureDateAndTime,arrivalDateAndTimedurationInMinutes,numberOfStopscarrier(operator name)price,currencytravelMode(train, bus, ferry, flight)deeplink— URL to Omio booking page for that specific connection
Request parameters:
departureLocation,arrivalLocation(city names)departureDate(YYYY-MM-DD)preferredTravelMode(train, bus, ferry, flight)currency(ISO 4217)limit,offsetminDepartureTime,maxDepartureTimesortingField(price, departureTime, arrivalTime)sortingOrder(ascending, descending)
Coverage: 1,000+ transport partners across Europe, trains + buses + ferries + flights.
Access:
- Signup via impact.com:
app.impact.com/campaign-promo-signup/GoEuro-Travel-Partner-Program.brand - Free to join, anyone with a website/app
- Review within 14 business days
- Contact:
affiliates@omio.com
Commission: Performance-based per redirect, scales with ROI. Minimum EUR 100 payout threshold.
Open question: Whether the Search API is available to all affiliates or only higher-tier partners. The ChatGPT plugin endpoint (omio.com/b2b-chatgpt-plugin/schedules) is locked behind auth, but the affiliate Search API may be a separate endpoint. Need to apply and ask.
Next step: Email affiliates@omio.com:
“We’re building a travel assistant app and want to integrate your Search API to show train connections with booking deeplinks. Is the Search API available to affiliate partners, and what are the requirements to get API credentials?”
All Aboard — Full booking API (not affiliate model)
Model: You ARE the ticket seller. The API handles search → pricing → booking → PDF ticket delivery. No booking links to external providers — you book through the API directly.
Why rejected: Selling tickets directly means we’re the merchant of record — refund liability, consumer protection, VAT, etc. Not our desired business model.
Coverage (for reference): DB, SNCF, TGV, OUIGO, Eurostar, Trenitalia, Trenord, Renfe, OBB, SBB, NS, SNCB, SJ, Vy, DSB, CD, ZSSK, MAV, CFL, plus Interrail/Eurail passes. GraphQL API, self-serve signup at allaboard.eu/join, docs at docs.allaboard.eu.
SerpAPI Google Trains — Search only, no booking links
Model: Scrape Google Search answer box (transport_options type) for train connections.
What it returns: Departure/arrival times, duration, fare, changes, summary (cheapest/fastest/daily count).
Tested successfully on 8+ European routes (domestic + cross-border). Costs 1 SerpAPI credit per query.
Why rejected as primary: No booking links per connection, no operator/carrier info, locale-dependent (gl=us/gb/ch only), currency tied to locale, no date API parameter. Could serve as a supplementary data source.
Trainline Deep Links — No search API
Model: Affiliate via Tradedoubler/Partnerize. You construct booking URLs with route+date, user lands on Trainline’s pre-filled search results.
Deep link format: https://www.thetrainline.com/book/results?journeySearchType=single&origin={station}&destination={station}&outwardDate={YYYY-MM-DDTHH:MM:SS}
Why rejected as primary: No Search API — can’t get connection details from Trainline, only link to them. Would need a separate data source + constructed links. The link opens the results page, not a specific connection.
Coverage: 270+ operators, 45 countries — largest in Europe.
Others Evaluated
| Provider | Model | Why rejected |
|---|---|---|
| Trainline Partner Solutions (TPS) | Enterprise API | Enterprise-only, ~12 weeks integration, no self-serve |
| Distribusion | Enterprise API | Partnership required, no self-serve |
| Rail Europe (ERA) | Agency/partner API | Commercial agreement required |
| Amadeus / Sabre / Travelport | GDS (SOAP) | Enterprise contracts, complex, expensive |
| DB transport.rest (v6) | Community wrapper | HAFAS shut down, low rate limits, Germany-only, no prices |
| Lyko | Booking API | 8 operators only, unclear access, no docs |
| Save A Train | Booking API | Small company, unclear coverage, no public docs |
| Google Transport Features API | Partner program | Requires formal Google partnership, not self-serve |
Fallback Strategy
If Omio Search API access is denied or too limited:
- SerpAPI trains for connection data (times, prices) — already integrated provider
- Trainline deep links for booking — construct URL per route+date
- Transitous/MOTIS for supplementary timetable data — already stubbed
This gives us search results with prices + a booking link, but the booking link opens a search results page (not a specific connection) and there’s no operator info from SerpAPI.
SerpAPI Trains — Technical Details (for fallback)
Query: engine=google, q="train {origin} to {destination} tickets", gl=gb
Returns answer_box.type = "transport_options" with:
title,from,to,date,cheapest,fastest,daily_trainsroutes[]:time,duration,fare,changes,fast
Limitations:
- Only triggers with
gl=us,gl=gb,gl=ch— NOTgl=de/fr/it/es/nl/at - No operator/carrier info per connection
- Currency tied to locale (GBP for
gl=gb) - Date must be in query text, no API parameter
- No booking links
Tested routes (all working with gl=gb):
- Munich → Berlin: 36 connections, 34 with fares
- Paris → London: 24 connections, 24 with fares
- Rome → Milan: 84 connections, 83 with fares
- Amsterdam → Brussels: 39 connections, 34 with fares
- Barcelona → Madrid: 38 connections, 38 with fares
- Vienna → Prague: 20 connections, 8 with fares
- Stockholm → Copenhagen: 36 connections, 19 with fares
- Hamburg → Frankfurt: 39 connections, 38 with fares
Deutsche Bahn Internal API — Direct Price Access (Reverse-Engineered)
Added: 2026-05-01 Source: Reverse-engineered from bahn.de, DB Navigator app, and the db-vendo-client open-source project.
Deutsche Bahn has no official public API for prices. However, two internal APIs used by their own products (bahn.de and DB Navigator app) return journey connections with prices, require no API key, and are well-documented by the open-source community.
Available APIs
| API | Base URL | Used by | API Key | Prices |
|---|---|---|---|---|
| Vendo Navigator API | https://app.services-bahn.de/mob/ |
DB Navigator app | No | Yes |
| Vendo bahn.de API | https://int.bahn.de/web/api/ |
bahn.de website | No | Yes |
| Old Navigator host | https://app.vendo.noncd.db.de/mob/ |
DB Navigator app | - | DNS gone as of 2026-07-29 |
| Old Sparpreis API | https://ps.bahn.de/preissuche/ |
(dead, DNS gone) | - | - |
Recommended: Navigator API — more stable, used by the official app, no bot detection (unlike bahn.de which has aggressive Akamai protection).
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/mob/angebote/fahrplan |
POST | Route search with prices |
/mob/angebote/tagesbestpreis |
POST | Best price per day (calendar view) |
/mob/angebote/recon |
POST | Refresh journey (detailed ticket options) |
/mob/location/search |
POST | Station/location autocomplete |
/mob/zuglauf/{id} |
GET | Train run details |
/mob/bahnhofstafel/abfahrt |
GET | Departure board |
Request Format — POST /mob/angebote/fahrplan
Headers:
Content-Type: application/x.db.vendo.mob.verbindungssuche.v9+json
Accept: application/x.db.vendo.mob.verbindungssuche.v9+json
X-Correlation-ID: {uuid}_{uuid}
Accept-Language: en
Body (Berlin → München, 1 adult, 2nd class, no BahnCard):
{
"autonomeReservierung": false,
"einstiegsTypList": ["STANDARD"],
"fahrverguenstigungen": {
"deutschlandTicketVorhanden": false,
"nurDeutschlandTicketVerbindungen": false
},
"klasse": "KLASSE_2",
"reiseHin": {
"wunsch": {
"abgangsLocationId": "A=1@O=Berlin Hbf@X=13369549@Y=52525589@U=80@L=8011160@B=1@p=1234567890@",
"zielLocationId": "A=1@O=München Hbf@X=11558339@Y=48140229@U=80@L=8000261@B=1@p=1234567890@",
"alternativeHalteBerechnung": true,
"verkehrsmittel": ["ALL"],
"zeitWunsch": {
"reiseDatum": "2026-05-15T10:00:00.000+02:00",
"zeitPunktArt": "ABFAHRT"
}
}
},
"reisendenProfil": {
"reisende": [
{
"ermaessigungen": ["KEINE_ERMAESSIGUNG KLASSENLOS"],
"reisendenTyp": "ERWACHSENER"
}
]
},
"reservierungsKontingenteVorhanden": false
}
Key Request Parameters
| Parameter | Values | Notes |
|---|---|---|
klasse |
KLASSE_1, KLASSE_2 |
Travel class |
zeitPunktArt |
ABFAHRT, ANKUNFT |
Departure or arrival time |
verkehrsmittel |
["ALL"], ["ICE"], ["EC_IC"], ["REGIONAL"] |
Transport filter |
reisendenTyp |
ERWACHSENER (27-64), SENIOR (65+), JUGENDLICHER (15-26), KIND (6-14) |
Traveller type |
ermaessigungen |
KEINE_ERMAESSIGUNG KLASSENLOS, BAHNCARD25 KLASSE_2, BAHNCARD50 KLASSE_2 |
Discount cards |
maxUmstiege |
integer | Max transfers |
fahrradmitnahme |
boolean | Bike transport |
deutschlandTicketVorhanden |
boolean | Has Deutschland-Ticket |
Location IDs use HAFAS format: A=1@O={name}@X={lon*1e6}@Y={lat*1e6}@U=80@L={evaNumber}@B=1@p=...@
The L= value is the EVA station number. Obtain via /mob/location/search.
Response Format (simplified)
{
"verbindungen": [
{
"verbindungsAbschnitte": [
{
"abgangsOrt": { "name": "Berlin Hbf", "extId": "8011160" },
"ankunftsOrt": { "name": "München Hbf", "extId": "8000261" },
"abgangsDatum": "2026-05-15T10:00:00",
"ankunftsDatum": "2026-05-15T14:00:00",
"verkehrsmittel": { "name": "ICE 1007", "produktGattung": "ICE" },
"abgangsGleis": "8",
"ankunftsGleis": "22"
}
],
"angebotsPreis": { "betrag": 29.90, "waehrung": "EUR" },
"abPreis": { "betrag": 17.90, "waehrung": "EUR" },
"hasTeilpreis": false,
"rekontext": "..."
}
]
}
Price fields:
angebote.preise.gesamt.ab.betrag— Starting from price in sampled Navigator responsesangebote.preise.gesamt.ab.waehrung— Currency, usuallyEURangebote.preise.istTeilpreis— Partial fare flag (only covers part of the route), useful whendeutschlandTicketVorhanden=true- Use
rekontextwith/mob/angebote/reconto get detailed ticket options (Super Sparpreis, Sparpreis, Flexpreis with conditions)
Observed 2026-07-29:
Potsdam Hbf → München Hbfwithout Deutschlandticket returned80.99 EUR,istTeilpreis=false, with S-Bahn + ICE sections.- The same route with
deutschlandTicketVorhanden=truereturned67.99 EUR,istTeilpreis=true; the quoted fare is the paid long-distance portion. Berlin Hbf → München Hbfwith Deutschlandticket returned67.99 EUR,istTeilpreis=false; the pass did not cover a separate regional feeder portion.- Product filters such as
REGIONAL,ICE,EC_IC, andSBAHNreturned HTTP 400 in sampled calls; keep usingverkehrsmittel: ["ALL"]until the current filter contract is verified.
Common EVA Station Numbers
| Station | EVA |
|---|---|
| Berlin Hbf | 8011160 |
| München Hbf | 8000261 |
| Frankfurt(Main)Hbf | 8000105 |
| Hamburg Hbf | 8002549 |
| Köln Hbf | 8000207 |
| Stuttgart Hbf | 8000096 |
| BER Airport | 8011201 |
Rate Limiting & Anti-Bot Notes
- Navigator API (
app.services-bahn.de) — no known bot detection in low-volume tests, designed for mobile app traffic. - bahn.de API (
int.bahn.de) — aggressive Akamai bot detection, returns 403/751 for automated browsers. Avoid for server-side use. - Use a realistic
User-Agent(e.g.,DBNavigator/Android/25.18.2). - No official rate limits documented. These are unofficial APIs — DB can change or block them at any time.
Transitous / MOTIS Notes
Observed 2026-07-29:
- Generic user agents are rejected with HTTP 403; set an application-specific
User-Agentand follow the Transitous API usage policy. - Current endpoints are under
https://api.transitous.org/api/, with geocoding at/api/v1/geocodeand planning at/api/v6/plan. /api/v6/plansupports useful timetable features:via, coordinate/radius searches, transit mode filters, transfer controls, realtime/cancelled flags, and GTFS source feed references.withFares=truereturned emptyeffectiveFareLegProductsarrays for sampled Germany and Germany-France routes, so do not treat Transitous as a pricing source.- Geocode ranking must prioritize textual and geographic match before mode richness; a naive station ranking can select a similarly named station in another country.
Reference Implementation
The db-vendo-client (JS/Node, 190 stars, active as of Oct 2025) is a full client for both APIs. It handles request formatting, response parsing, price extraction, and pagination. Use as reference for our Python provider.
Relevance to OpenMates
This API is useful as a Germany-specific fallback or supplement to Omio:
- Pro: Free, no API key, real-time prices, detailed ticket breakdown, platform numbers
- Con: Germany-only (some cross-border via DB), unofficial/unstable, no booking deeplinks, no affiliate revenue
- If used, provider wrapper goes in
backend/shared/providers/deutsche_bahn.py - Two-step flow: location search → route search with prices
- Cache station EVA lookups (they rarely change)