Developer portal · Architecture

Architecture & infrastructure

Trackser Live runs entirely on Cloudflare's edge — one Worker, one Durable Object per line, and object storage. No origin servers, no databases to babysit.

Runtime

The system is a single Cloudflare Worker (trackser-live) with one Durable Object class, LineBuilderDO. There is no Node server, no container, and no relational database. Everything — ingestion, processing, storage and serving — happens within Workers.

One Durable Object per line

Each Underground line is owned by its own LineBuilderDO instance, addressed by the line ID. That DO holds the line's persistent state — direction hints, vehicle-ID memory, stall history — and is solely responsible for building that line's snapshots. The DO is SQLite-backed, so its state survives restarts and deploys.

Isolating state per line means a problem on one line can't corrupt another, and each line rebuilds independently.

The build pipeline

Every build cycle, each line's DO runs the same pipeline:

cron / alarm
   │
   ▼
LineBuilderDO.buildOnce()
   ├─ fetchTrackerNet()   → per-station TfL TrackerNet (XML)
   ├─ fetchUnified()      → TfL Unified /Line/{ids}/Arrivals (JSON)
   ├─ mergeTrains()       → collapse many sightings → one record per train
   ├─ normalizeAndEnrich()→ direction, vehicle ID, stall state (uses DO hints)
   └─ applyMetStoppingPattern()  → Metropolitan working-timetable overlay
        │
        ▼
   publish gzip JSON → R2  +  archive a snapshot

The result is a compact, enriched snapshot written to R2, ready to be served. Clients never trigger this work — they only read the latest published snapshot.

Build cadence

Request flow

An incoming API request passes through these stages:

  1. Security headers are applied to every response.
  2. Host allow-list — requests to the wrong host are 308-redirected to the primary host.
  3. HTTPS enforcement — plain HTTP is 308-redirected.
  4. Path allow-list — unknown paths get a 404 HTML page.
  5. Key check — the route handler validates the API key, scope, rate limit and historical window.
  6. Serve — the handler reads the relevant pre-built R2 object (or DO state) and returns JSON. A missing live snapshot returns 202 warming and triggers a build.

Storage

State is spread across R2 buckets and KV namespaces, each with a clear role:

BindingTypeRole
R2_BUCKETR2Live snapshots and the replay archive.
COLD_BUCKETR2Aged-out replay archive (cold storage).
TIMETABLES_R2R2Working-timetable files.
API_USAGE_R2R2Daily per-key usage records.
API_STATUS_R2R2Structured logs (JSONL streams).
API_KEYS_KVKVAPI key store.
API_USAGEKVUsage counters and rate-limit windows.
TIMETABLE_SCHEDULEKVPer-line, per-date timetable overrides.
ADMIN_NOTICEKVThe status-page notice banner.
RELEASES_KVKVRelease history behind /releases.

Upstream TfL credentials and per-line heartbeat tokens live in Cloudflare Secrets Store, never as plaintext variables.

Archiving & retention

Every map snapshot is archived. Full-resolution data is kept for 14 days in the primary bucket, then downsampled and moved to cold storage beyond that. Per-day journey records are retained separately. A companion trackser-cleanup Worker applies the retention policy; this system only exposes its policy controls.

Caching

Cache behaviour depends on the data class:

Data classCache-Control
Live snapshots, replay readsno-store, max-age=0
Historical & timetable datapublic, max-age=3600
Station registrypublic, max-age=86400
Stats & logspublic, max-age=300

Security & CORS

Environments

Two environments run side by side — production (trackser.app) and preview (preview.trackser.app) — with separate R2 buckets and shared KV where appropriate.

🩺

Want to see the pipeline's live health? The status page reports per-line build freshness and independent uptime monitoring.