Skip to content

Technology

Technical architecture

Each installation requests fares and journey data itself. The WebView presents the results. A native background process checks price alerts and active journeys. Separate stores protect different kinds of data.

An installed Bahnsparer app

Runtime

Native HTTP
URLSession on iOS, OkHttp on Android. The request goes directly to the data source.
WebView
Presents fares, combines live and historical data, and evaluates the model.
Background runner
Checks saved days and active journeys. Notices are created locally.

Storage

WebView storage
Settings, routes, price histories and journeys with backups.
CapacitorKV
Working state for price alerts, journeys and the native background process.
Keychain or Keystore
Only the optional DB sign-in key; never in iCloud or WebView storage.
Private CloudKit database
Optional sync of selected app data in the user's iCloud account.
No Bahnsparer proxy processes rail data. Website subscriptions and update checks remain separate web functions.
Contents · 4 topics

Why requests run natively

The rail hosts used do not allow browser requests from another website. DB also blocks several common script runtimes by their TLS fingerprint, so an ordinary web client fails before it can process the response.

In the installed app, Capacitor forwards the standard fetchcall to URLSession on iOS or OkHttp on Android. These native network stacks send the request directly to DB or Flix, with no Bahnsparer proxy in between.

Only the local development environment has a relay: Vite passes allowed test requests to curl. This path is not part of the production bundle and cannot process requests from an installed app.

WebView and background process divide the work

WebView

It runs searches, filters connections, evaluates reliability and stores price histories. On launch and return to the app, it reads the current background-process state.

Background runner

It records active journeys first, then checks a rotating subset of watched travel days. This keeps the request budget bounded. iOS and Android decide when a run may occur, so the app promises no interval.

The runner stores new lows and recorded arrivals. Local price and passenger-rights notices are created directly on the device. Neither a push provider nor a Bahnsparer server receives this data.

Which data lives in each store

Storage
WebView storage
Contents
Einstellungen, Routen, Fahrten und Preisverläufe
Boundary
Lokal mit lesbarer Sicherungs- und Wiederherstellungskopie
Storage
CapacitorKV
Contents
Preisalarme, Fahrten, Beobachtungen und Runner-Fortschritt
Boundary
Für WebView und Background Runner, nicht für iCloud
Storage
Keychain or Keystore
Contents
Optionaler DB-Aktualisierungsschlüssel
Boundary
Native sichere Ablage, nie im App-Datensatz
Storage
Private CloudKit database
Contents
Preisalarme, Fahrten, Routen und Sucheinstellungen
Boundary
Nur nach Aktivierung, im privaten iCloud-Konto

Price histories, notification state and DB sign-in data are not synced through iCloud. The private CloudKit database first pulls server changes, merges records using stable IDs and timestamps, then uploads local changes.

What continues to work after a failure

Each interface has its own client. If carriage order fails, for example, fare search and the train run remain usable. Missing live data is not reported as on time or empty.

Saved routes, previous price observations and the reliability model remain on the device. A failed network request does not delete that state.

The exact hosts and request paths are documented under Data interfaces. The optional data that leaves the device is explained in the privacy notice.