OpenMates Docs Open Chat

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...

[T:documentation.sender_name]

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

  1. Returns full connection details per result (departure/arrival times, duration, changes, operator/carrier, price)
  2. Returns a booking link per connection to an external booking provider
  3. We are an affiliate/comparison layer, NOT the ticket seller
  4. Accessible to individual developers (not enterprise-only)
  5. European coverage (domestic + cross-border)

APIs Evaluated

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, arrivalLocation
  • departureDateAndTime, arrivalDateAndTime
  • durationInMinutes, numberOfStops
  • carrier (operator name)
  • price, currency
  • travelMode (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, offset
  • minDepartureTime, maxDepartureTime
  • sortingField (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.

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.

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:

  1. SerpAPI trains for connection data (times, prices) — already integrated provider
  2. Trainline deep links for booking — construct URL per route+date
  3. 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_trains
  • routes[]: time, duration, fare, changes, fast

Limitations:

  • Only triggers with gl=us, gl=gb, gl=ch — NOT gl=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 responses
  • angebote.preise.gesamt.ab.waehrung — Currency, usually EUR
  • angebote.preise.istTeilpreis — Partial fare flag (only covers part of the route), useful when deutschlandTicketVorhanden=true
  • Use rekontext with /mob/angebote/recon to get detailed ticket options (Super Sparpreis, Sparpreis, Flexpreis with conditions)

Observed 2026-07-29:

  • Potsdam Hbf → München Hbf without Deutschlandticket returned 80.99 EUR, istTeilpreis=false, with S-Bahn + ICE sections.
  • The same route with deutschlandTicketVorhanden=true returned 67.99 EUR, istTeilpreis=true; the quoted fare is the paid long-distance portion.
  • Berlin Hbf → München Hbf with Deutschlandticket returned 67.99 EUR, istTeilpreis=false; the pass did not cover a separate regional feeder portion.
  • Product filters such as REGIONAL, ICE, EC_IC, and SBAHN returned HTTP 400 in sampled calls; keep using verkehrsmittel: ["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-Agent and follow the Transitous API usage policy.
  • Current endpoints are under https://api.transitous.org/api/, with geocoding at /api/v1/geocode and planning at /api/v6/plan.
  • /api/v6/plan supports useful timetable features: via, coordinate/radius searches, transit mode filters, transfer controls, realtime/cancelled flags, and GTFS source feed references.
  • withFares=true returned empty effectiveFareLegProducts arrays 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)