API — v1 beta

Train Badger API

Two ways to pull UK Delay Repay data programmatically: plain REST endpoints, and a remote MCP server for LLM clients like Claude.

v1 beta — shapes may change, no SLA. The lookup endpoints and tools above are unauthenticated and rate-limited. A handful of routes below read your own account data and need a bearer token instead. Treat responses as best-effort; we may change field names, add fields, or adjust limits without notice while this stays in beta.
Scope: this documents Train Badger's Delay Repay API — including the authenticated routes that return your own Delay Repay data. Other endpoints you may observe are internal, undocumented, and may change or disappear without notice.

API terms of use (interim)

  • Data is provided as-is from National Rail and Network Rail sources and may contain errors.
  • You're solely responsible for the accuracy of any claim you or your agent submits.
  • Automated interaction with an operator's claim form must respect that operator's own terms of use.
  • An agent should present a claim to you for review before submitting it.
  • Access may be revoked for misuse, at Train Badger's discretion.

These API terms are in beta and will be replaced by the formal Swipe Stack Ltd API terms (see /terms) — F (#92) owns the formal text.

Agent workflow: filing your Delay Repay automatically

The headline use case for this API: point an AI agent at your Delay Repay journeys — pre-registering future travel up front, then filing claims on each operator's website once delays are known — and have it mark them filed so nothing gets double-submitted. Four steps, using a personal API key so the agent never handles your password.

0. Pre-register future travel (optional)

For a train you have not taken yet, register it before its scheduled departure with POST /api/tracked-journeys (needs the delay-repay:track scope) — origin, destination, service date (within today..today+30 days), exact scheduled departure, and ticket type. An unknown time returns the nearest scheduled departures so the agent can correct itself. After travel, Train Badger resolves the outcome and the journey joins the step-1 work list below. Or, for an MCP-native agent, call the track_journey tool instead.

Request
curl -X POST "https://api.trainbadger.app/api/tracked-journeys" \
  -H "Authorization: Bearer tb_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"origin": "EUS", "destination": "COV", "serviceDate": "2026-10-15", "scheduledDeparture": "08:15", "ticket": {"type": "single", "pricePence": 1250}}'

1. Read your eligible journeys

GET /api/journeys?eligible=true with a delay-repay:read key returns your unclaimed, eligible journeys — origin/destination, delay minutes, and (via GET /api/journeys/claim-summary) each operator's claimUrl and claim steps. GET /api/journeys also returns your seasonTicket and fares, which most operator claim forms ask for. GET /api/journeys/claim-summary includes the same seasonTicket and fares alongside the work list, and the MCP get_my_delay_repay_claims tool returns journeys plus that claim summary (with ticket data) in one call for an MCP-native agent. Handing a key to an agent delegates access to this journey and ticket data — mint one key per agent and revoke it when done.

Only verified journeys are claimable. Every journey carries a verificationStatus of "verified" (TRUST/Darwin-confirmed), "estimated" (archived from an estimate, not yet confirmed), or "user_confirmed" (an estimate the user confirmed in the app — claimable, and included in eligible=true by default). eligible=true excludes unconfirmed "estimated" rows by default — pass includeUnconfirmed=true to see them anyway for display purposes. Either way, trying to file an unconfirmed estimated journey (step 3, below) is rejected. Journeys the user rejected in the app never appear in any listing.

Request
curl "https://api.trainbadger.app/api/journeys/claim-summary" \
  -H "Authorization: Bearer tb_live_<your-key>"

See GET /api/journeys/claim-summary below for the full response shape — operators[].claimUrl and operators[].journeys[] are the work list.

2. File the claim

Your agent visits each claimUrl and submits the operator's Delay Repay form using the journey and fare data from step 1. This part happens on the operator's own website — Train Badger has no API for it, since every operator's claim form is different.

3. Mark it filed

Once the claim is submitted, mark the journey claimed so it drops out of future work lists and doesn't get filed twice. Use a key with the delay-repay:write scope. Only journeys with verificationStatus: "verified" or "user_confirmed" can be marked claimed — an unconfirmed "estimated" journey is rejected with 400 (REST) or a friendly error (MCP), since it hasn't been confirmed yet.

Request
curl -X PUT "https://api.trainbadger.app/api/journeys/42/claimed" \
  -H "Authorization: Bearer tb_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"claimed": true}'

Or, for an MCP-native agent, call the mark_delay_repay_claimed tool with { "journeyId": 42 } instead — idempotent either way: marking an already-claimed journey succeeds with a note rather than erroring, so a retried or double-run agent step is harmless.

Full sequence (REST, curl)

1. Read the work list
curl "https://api.trainbadger.app/api/journeys/claim-summary" \
  -H "Authorization: Bearer tb_live_<your-key>"
# -> { "operators": [ { "operatorCode": "GW", "claimUrl": "https://www.gwr.com/delay-repay",
#      "journeys": [ { "id": 42, "delayMinutes": 20, ... } ], ... } ], ... }
2. (your agent files the claim at claimUrl — outside this API)
# ...
3. Mark it filed
curl -X PUT "https://api.trainbadger.app/api/journeys/42/claimed" \
  -H "Authorization: Bearer tb_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"claimed": true}'
# -> { "success": true }

Full sequence (MCP)

Configure your MCP client with a delay-repay:read + delay-repay:write + delay-repay:track key (track covers the pre-register/withdraw tools below; list needs read):

.mcp.json
{
  "mcpServers": {
    "trainbadger": {
      "type": "http",
      "url": "https://api.trainbadger.app/mcp",
      "headers": {
        "Authorization": "Bearer tb_live_<your-key>"
      }
    }
  }
}

Then in conversation: call get_my_delay_repay_claims to get the work list, file each claim at its claimUrl, and call mark_delay_repay_claimed with each journey's id once filed.

REST API

Three read-only endpoints — no auth required. REST-only assistant integrations (an environment that can call HTTP but doesn't speak MCP) can be built entirely from these, using the OpenAPI spec below as the contract.

Download OpenAPI spec

OpenAPI 3.1, hand-written to match these exact response shapes. Public like this page.

GET /public/journey-check

Look up how a specific train journey actually performed on a given date — scheduled vs. actual times, delay minutes, cancellations, and Delay Repay eligibility for every matching service.

ParamFormatNotes
fromCRS codeOrigin station, e.g. PAD
toCRS codeDestination station, e.g. RDG
dateYYYY-MM-DDMust be in the past, within 90 days
timeHHMMScheduled departure, no colon, e.g. 0815
Rate limit: 10 requests/minute per IP Data window: recent journeys — coverage is best in roughly the last month; dates up to 90 days back are accepted but older results may be sparse or empty
Request
curl "https://api.trainbadger.app/public/journey-check?from=PAD&to=RDG&date=2026-09-01&time=0815"
Response — 200
{
  "services": [
    {
      "scheduledDeparture": "08:15",
      "actualDeparture": "08:17",
      "scheduledArrival": "09:02",
      "actualArrival": "09:25",
      "delayMinutes": 23,
      "operatorCode": "GW",
      "operatorName": "Great Western Railway",
      "isCancelled": false,
      "delayRepay": {
        "eligible": true,
        "scheme": "DR15",
        "band": "25%",
        "claimUrl": "https://www.gwr.com/delay-repay"
      }
    }
  ],
  "origin": { "crs": "PAD", "name": "London Paddington" },
  "destination": { "crs": "RDG", "name": "Reading" }
}

Validation errors (bad CRS code, date not in the past, malformed time) return 400 with an { "error": "..." } body. Upstream failures return 502.

GET /public/delay-repay/{operator}

Look up a UK train operator's Delay Repay scheme. {operator} accepts an ATOC code (e.g. GW) or an operator name (e.g. Great Western Railway), matched case-insensitively.

Rate limit: 30 requests/minute per IP (guest tier)
Request
curl "https://api.trainbadger.app/public/delay-repay/GW"
Response — 200
{
  "operatorCode": "GW",
  "operatorName": "Great Western Railway",
  "scheme": "DR15",
  "thresholdMinutes": 15,
  "bands": [
    { "minMinutes": 15, "band": "25%" },
    { "minMinutes": 30, "band": "50%" },
    { "minMinutes": 60, "band": "100%" },
    { "minMinutes": 120, "band": "100% return" }
  ],
  "claimUrl": "https://www.gwr.com/delay-repay",
  "claimTimeLimit": "28 days"
}

Unknown operator returns 404 with { "error": "Unknown operator \"...\"" }.

GET /public/crs/{query}

Station code lookup — a supporting endpoint for journey-check. Search UK National Rail stations by name or CRS code fragment.

Rate limit: 30 requests/minute per IP (guest tier) Cache: 24h
Request
curl "https://api.trainbadger.app/public/crs/paddington"
Response — 200
[
  { "crsCode": "PAD", "stationName": "London Paddington" }
]

MCP server

A remote Model Context Protocol server exposing eight tools an LLM client can call directly — the three lookups, two claim tools, and three tracked-journey tools — streamable HTTP, stateless (no session to manage).

POST https://api.trainbadger.app/mcp
Transport: Streamable HTTP (stateless) Auth: none Rate limit: 10 requests/minute per IP

A single JSON-RPC batch request is rejected outright (-32600 Invalid Request) rather than counted as one call — see the tool descriptions for exact schemas via tools/list.

Adding it to a client

Any MCP client that supports a remote streamable-HTTP server can point directly at the URL above. For Claude Code, add it to .mcp.json:

.mcp.json
{
  "mcpServers": {
    "trainbadger": {
      "type": "http",
      "url": "https://api.trainbadger.app/mcp"
    }
  }
}

Claude Desktop and other clients that only speak stdio can bridge to it with a local proxy such as mcp-remote. Check your client's docs for its remote-server config shape.

Tools

check_journey_delay

Same data as the REST endpoint above, as an MCP tool call.

Input
{ "from": "PAD", "to": "RDG", "date": "2026-09-01", "time": "08:15" }
Output (text content)
Journey check: London Paddington (PAD) -> Reading (RDG) on 2026-09-01 around 08:15
{
  "origin": { "crs": "PAD", "name": "London Paddington" },
  "destination": { "crs": "RDG", "name": "Reading" },
  "services": [ { "scheduledDeparture": "08:15", "delayMinutes": 23, "delayRepay": { "eligible": true, "scheme": "DR15", "band": "25%" }, ... } ]
}

(Data covers recent journeys — coverage is best in roughly the last month;
dates up to 90 days back are accepted but older results may be sparse or empty.)

Contains data from National Rail and Network Rail

get_delay_repay_scheme

Look up an operator's Delay Repay scheme. Accepts an ATOC code or an operator name, matched case-insensitively.

Input
{ "operator": "Great Western Railway" }
Output (text content)
Great Western Railway (GW) runs DR15. Minimum delay for compensation: 15 minutes.
{
  "operatorCode": "GW",
  "operatorName": "Great Western Railway",
  "scheme": "DR15",
  "thresholdMinutes": 15,
  "bands": [
    { "minMinutes": 15, "band": "25%" },
    { "minMinutes": 30, "band": "50%" },
    { "minMinutes": 60, "band": "100%" },
    { "minMinutes": 120, "band": "100% return" }
  ],
  "claimUrl": "https://www.gwr.com/delay-repay",
  "claimTimeLimit": "28 days"
}

Contains data from National Rail and Network Rail

search_stations

Search UK National Rail stations by name or CRS code fragment. Returns up to 15 matches.

Input
{ "query": "paddington" }
Output (text content)
[
  { "crsCode": "PAD", "stationName": "London Paddington" }
]

Contains data from National Rail and Network Rail

Invalid input on any tool (bad CRS code, unknown operator, empty query, …) returns an MCP tool error (isError: true content) rather than a crash or a raw exception.

get_my_delay_repay_claims

Requires authentication. Returns the caller's own journeys and Delay Repay claim-summary totals — the MCP equivalent of the two authenticated REST routes documented below, in one call. Send your account token as an Authorization: Bearer <token> HTTP header on the MCP request (not as a tool argument — MCP clients attach it at the transport level, same as any other bearer-auth API).

Input
{ "eligibleOnly": true, "limit": 20, "includeUnconfirmed": false }

When eligibleOnly is true (the default), journeys with verificationStatus: "estimated" are excluded — pass includeUnconfirmed: true to include them anyway, for display purposes only (they still can't be filed — see mark_delay_repay_claimed below).

Output (text content)
Your Delay Repay claims (1 of 1 matching journey returned)
{
  "journeys": [
    {
      "id": 42,
      "service_id": "svc-abc",
      "origin_crs": "PAD",
      "destination_crs": "RDG",
      "delay_minutes": 20,
      "delay_repay_eligible": 1,
      "delay_repay_band": "25%",
      "journey_date": "2026-09-01",
      "verificationStatus": "verified",
      ...
    }
  ],
  "total": 1,
  "claimSummary": {
    "operators": [ { "operatorCode": "GW", "totalUnclaimedPence": 1250, "journeyCount": 1, ... } ],
    "totalUnclaimedPence": 1250,
    "totalJourneyCount": 1
  }
}

Contains data from National Rail and Network Rail

No token, a malformed token, or one that's invalid/expired all return the same friendly isError message (we deliberately don't say which, to avoid leaking account state to a caller who isn't proven to own the token):

Output — auth failure (isError: true)
This tool needs your Train Badger account token. Mint a personal API key in the app (Settings › API Access) — or copy a login token from the account page — 
then configure your MCP client to send it as an Authorization: Bearer header.

track_journey

Requires authentication (and the delay-repay:track scope for an API key) — send the credential as an HTTP header on the /mcp request, as with get_my_delay_repay_claims above. Pre-registers a specific future train: the MCP equivalent of POST /api/tracked-journeys, with the same validation, timetable check and anti-abuse rules.

Input
{ "origin": "EUS", "destination": "COV", "serviceDate": "2026-10-15", "scheduledDeparture": "08:15", "ticket": { "type": "single", "pricePence": 1250 } }
Output (text content)
Tracked journey 3: EUS -> COV on 2026-10-15 at 08:15 (status: pending).
{ "journey": { "id": 3, "status": "pending", "...": "..." }, "attestation": "By registering this journey you confirm that you will travel on the service stated. If this journey becomes a claim, your agent must show the claim to you for approval before filing it. See the API terms: https://trainbadger.app/terms", "terms": "https://trainbadger.app/terms", "beta": true }

Contains data from National Rail and Network Rail

An unknown service returns an isError naming no_such_service with the nearest scheduled departures, so the agent can correct the time and retry.

list_tracked_journeys

Requires authentication (and the delay-repay:read scope for an API key — same as GET /api/tracked-journeys). Returns the caller's own pre-registered journeys and their resolution state — the MCP equivalent of GET /api/tracked-journeys.

Input
{ "status": "pending", "limit": 20 }
Output (text content)
Your tracked journeys (1 of 1 returned)
{ "journeys": [ { "id": 3, "origin": "EUS", "destination": "COV", "status": "pending", "...": "..." } ], "total": 1 }

Contains data from National Rail and Network Rail

withdraw_tracked_journey

Requires authentication (and the delay-repay:track scope for an API key). Cancels one of the caller's own pre-registered journeys — the MCP equivalent of DELETE /api/tracked-journeys/:id. A journey still pending before departure is deleted outright; anything else is marked withdrawn.

Input
{ "journeyId": 3 }
Output (text content)
Tracked journey 3 deleted.

Contains data from National Rail and Network Rail

Your account data (authenticated)

The routes below read or create data tied to your own Train Badger account rather than public lookups. Authenticate with a login token or a personal API key (see API keys) and send it as Authorization: Bearer <token> on every request (including MCP tool calls — see get_my_delay_repay_claims above).

Keep your token private. A token is tied to one account and works for 30 days from issue. Don't paste it into a shared chat, commit it to a repo, or embed it in a client-side app anyone else can inspect — anyone holding it gets full read and write access to that account across /api/*, not just the read-only routes documented here: they can mark journeys claimed, delete journey history, register push devices, and more. (A login token can also mint fresh API keys, so guard it as carefully as a password.)

GET /api/journeys

Your journey history: what Train Badger has recorded from your pinned/tracked trips, with Delay Repay eligibility already computed for each.

ParamFormatNotes
eligibletrueOptional. Only include Delay Repay-eligible journeys. Also excludes verificationStatus: "estimated" rows by default (see includeUnconfirmed).
includeUnconfirmedtrueOptional, only meaningful with eligible=true. Includes "estimated" journeys too, clearly statused — for display purposes, since they still can't be marked claimed.
limitintegerOptional, default 20, max 100.
offsetintegerOptional, default 0.
Auth: Bearer token
Request
curl "https://api.trainbadger.app/api/journeys?eligible=true&limit=20" \
  -H "Authorization: Bearer <token>"
Response — 200
{
  "journeys": [
    {
      "id": 42,
      "user_id": 7,
      "service_id": "svc-abc",
      "origin_crs": "PAD",
      "destination_crs": "RDG",
      "operator_code": "GW",
      "operator_name": "Great Western Railway",
      "scheduled_departure": "08:00",
      "actual_departure": "08:05",
      "scheduled_arrival": "08:30",
      "actual_arrival": "08:50",
      "arrival_estimated": 0,
      "is_cancelled": 0,
      "cancel_reason": null,
      "delay_reason": null,
      "delay_minutes": 20,
      "delay_repay_scheme": "DR15",
      "delay_repay_eligible": 1,
      "delay_repay_band": "25%",
      "delay_repay_claim_url": "https://www.gwr.com/delay-repay",
      "journey_date": "2026-09-01",
      "claimed": 0,
      "season_ticket_match": 0,
      "estimated_compensation_pence": 1250,
      "fare_match_id": null,
      "source": "app",
      "verificationStatus": "verified"
    }
  ],
  "total": 1,
  "seasonTicket": null,
  "fares": []
}

Field names are the raw journey_history columns (snake_case), not camelCased — this route returns exactly what's stored. seasonTicket and fares are the account's season-ticket record (or null) and saved fares, included alongside the journey page for client convenience. verificationStatus is the one derived (camelCase) field: "verified" when arrival_estimated = 0, "estimated" when 1 — an hasn't been confirmed yet. source is "app" for app-recorded journeys and "api" for outcomes the tracked-journey resolver wrote from an agent-registered row.

GET /api/journeys/claim-summary

Unclaimed, Delay Repay-eligible journeys grouped by operator, with per-operator and grand totals — the data behind the app's "claim your compensation" screen.

Auth: Bearer token
Request
curl "https://api.trainbadger.app/api/journeys/claim-summary" \
  -H "Authorization: Bearer <token>"
Response — 200
{
  "operators": [
    {
      "operatorCode": "GW",
      "operatorName": "Great Western Railway",
      "claimUrl": "https://www.gwr.com/delay-repay",
      "scheme": "DR15",
      "claimTimeLimit": "28 days",
      "claimSteps": [],
      "journeys": [
        {
          "id": 42,
          "journeyDate": "2026-09-01",
          "claimDeadline": "2026-09-29",
          "originCrs": "PAD",
          "originName": "London Paddington",
          "destinationCrs": "RDG",
          "destinationName": "Reading",
          "scheduledDeparture": "08:00",
          "actualArrival": "08:50",
          "delayMinutes": 20,
          "delayRepayBand": "25%",
          "estimatedCompensationPence": 1250,
          "isCancelled": false,
          "delayReason": null,
          "verificationStatus": "verified"
        }
      ],
      "totalUnclaimedPence": 1250,
      "journeyCount": 1,
      "nearestDeadline": "2026-09-29"
    }
  ],
  "totalUnclaimedPence": 1250,
  "totalJourneyCount": 1,
  "seasonTicket": null,
  "fares": []
}

Only journeys with claimed = 0, delay_repay_eligible = 1, and a claimable verificationStatus ("verified", or "user_confirmed" for an estimate the user confirmed in the app) are included — this is the agent workflow's work list, so an unconfirmed estimated journey is excluded from both the list and the totals, matching GET /api/journeys?eligible=true's default. Note this route's journey objects are camelCased (unlike the raw GET /api/journeys shape above) — it shares its per-journey shaping with the claim-details email, not with the plain journey list. It also returns your seasonTicket (or null) and fares — route, price(s), duration/expiry, the fields operator claim forms ask for — under the same delay-repay:read scope. No ticket numbers, booking references, or photos are stored or served; you supply that proof on the operator's site when filing.

PUT /api/journeys/:id/claimed

Mark one of your own journeys as claimed (or unclaimed). This is step 3 of the agent workflow above — call it once your agent has filed the claim at the operator's claimUrl, so the journey drops out of future claim-summary/work-list results and doesn't get filed twice. Idempotent in effect: re-marking an already-claimed journey just returns { "success": true } again.

Auth: Bearer token or API key with delay-repay:write
Request
curl -X PUT "https://api.trainbadger.app/api/journeys/42/claimed" \
  -H "Authorization: Bearer <token-or-tb_live_key>" \
  -H "Content-Type: application/json" \
  -d '{"claimed": true}'
Response — 200
{ "success": true }

Body claimed must be a boolean — 400 otherwise. 404 for an unknown id or one that isn't yours. An API key without the delay-repay:write scope gets 403 naming the missing scope. Only verified journeys can be filed: setting claimed: true on a journey with verificationStatus: "estimated" returns 400 with { "error": "This journey's arrival is estimated, not verified — it can't be marked claimed yet" }. Setting claimed: false (un-marking) is allowed regardless of verification status.

POST /api/tracked-journeys — pre-register a journey

Register a specific train before its scheduled departure: origin/destination CRS codes, serviceDate within today..today+30 days (London time), exact scheduledDeparture (HH:MM), ticket with a valid type, optional operatorCode and clientReference. The service must exist in the timetable — a miss returns 422 no_such_service with the nearest scheduled departures. Registering after the scheduled departure is refused with 409 departure_passed; legs that don't chain (each origin must continue the previous destination) are refused with 409 legs_not_chained. One train per directed route per day; at most 4 legs and 20 writes per day. When the write quota is exhausted the route returns 429 write_quota_exceeded with a Retry-After header (and retryAfterSeconds in the body) giving the seconds until the next London midnight, when the quota resets.

Auth: login token or API key with delay-repay:track
Request
curl -X POST "https://api.trainbadger.app/api/tracked-journeys" \
  -H "Authorization: Bearer tb_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"origin": "EUS", "destination": "COV", "serviceDate": "2026-10-15", "scheduledDeparture": "08:15", "ticket": {"type": "single", "pricePence": 1250}}'
Response — 201
{
  "journey": { "id": 3, "origin": "EUS", "destination": "COV", "serviceDate": "2026-10-15", "scheduledDeparture": "08:15", "status": "pending", "...": "..." },
  "attestation": "By registering this journey you confirm that you will travel on the service stated. If this journey becomes a claim, your agent must show the claim to you for approval before filing it. See the API terms: https://trainbadger.app/terms",
  "terms": "https://trainbadger.app/terms",
  "beta": true
}

The attestation is what the registration confirms. app carries a one-line pointer to the iOS app, but only on the account's first-ever tracked journey.

GET /api/tracked-journeys — list and fetch

The agent's work list: what it registered and what state each row is in (pending, awaiting_data, resolved, no_such_service, unverifiable, withdrawn). Filter with status, from/to service dates, limit (default 20, max 100) and offset. GET /api/tracked-journeys/:id fetches one row. Once a row resolves, it embeds the linked outcome as journey (same shape as a GET /api/journeys row); null while pending.

Auth: login token or API key with delay-repay:read
Request
curl "https://api.trainbadger.app/api/tracked-journeys?status=pending" \
  -H "Authorization: Bearer tb_live_<your-key>"

PATCH + DELETE /api/tracked-journeys/:id — change and withdraw

Ticket/client-reference edits apply in place until the linked journey is marked filed. An operator-only change applies in place while pending and pre-departure. Changing the train itself (origin/destination/date/time) is a one-time audited replacement returning a new row id. A replacement whose new departure has already passed is refused with 409 departure_passed; replacement legs that don't chain are refused with 409 legs_not_chained. DELETE removes a still-pending pre-departure row outright, otherwise marks it withdrawn.

Auth: login token or API key with delay-repay:track
Request
curl -X DELETE "https://api.trainbadger.app/api/tracked-journeys/3" \
  -H "Authorization: Bearer tb_live_<your-key>"

GET /api/account/export — export your data

Self-serve export of everything held on the account (profile, consent, tracked journeys, journey history, favourites, tickets, briefs, pins, key metadata, audit log, …). Secrets are never exported — no password hashes, tokens, or push credentials. Rate-limited to 5/hour; served as a download.

Auth: login token only Rate limit: 5 requests/hour per account
Request
curl "https://api.trainbadger.app/api/account/export" \
  -H "Authorization: Bearer <your-login-token>"

Adding your token to an MCP client

For get_my_delay_repay_claims, configure your MCP client to send the bearer token as an HTTP header on the /mcp request itself:

.mcp.json
{
  "mcpServers": {
    "trainbadger": {
      "type": "http",
      "url": "https://api.trainbadger.app/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

The three lookup tools (check_journey_delay, get_delay_repay_scheme, search_stations) ignore this header entirely — it's only read by the five authenticated tools: get_my_delay_repay_claims, mark_delay_repay_claimed, track_journey, list_tracked_journeys and withdraw_tracked_journey. Either token type works here: a login token or a tb_live_ API key with the right scope.

mark_delay_repay_claimed

Requires authentication (and the delay-repay:write scope, if you're using an API key). The MCP equivalent of PUT /api/journeys/:id/claimed above — the "mark filed" step of the agent workflow. Idempotent: marking an already-claimed journey succeeds with a note rather than an error.

Input
{ "journeyId": 42 }
Output (text content)
Journey 42 marked as claimed.

Contains data from National Rail and Network Rail

Re-calling it on the same journey returns Journey 42 was already marked as claimed — no change made. instead of an error — safe for a retried or double-run agent step. An unknown journey id, or one belonging to another account, returns a not-found isError. Only verified journeys can be filed: a journey with verificationStatus: "estimated" returns an isError saying its arrival is estimated, not verified, and can't be marked claimed yet.

API keys

A personal API key (tb_live_<64 hex chars>) is a long-lived, revocable, scoped alternative to a login token — mint one so an agent or script can keep working without re-logging in every 30 days. Train Badger stores only its SHA-256 hash; the full value is shown once, at creation, and never again. API keys are gated per-account — Settings › API Access in the app shows whether yours is enabled; if it isn't, contact [email protected].

ScopeGrants
delay-repay:readGET /api/journeys, GET /api/journeys/claim-summary, GET /api/tracked-journeys and GET /api/tracked-journeys/:id, and the MCP get_my_delay_repay_claims and list_tracked_journeys tools. Always present — you can't mint a key without it.
delay-repay:writePUT /api/journeys/:id/claimed, and the MCP mark_delay_repay_claimed tool. Opt-in.
delay-repay:trackPOST/PATCH/DELETE /api/tracked-journeys*, and the MCP track_journey and withdraw_tracked_journey tools. Opt-in (every key already carries read alongside).

A key is accepted only on the routes/tools above — every other /api/* route, and /api/keys itself, rejects API key auth with 403 and requires a normal login token instead. A key never widens access: it works only on the routes/tools above, exactly as far as its scopes allow.

POST /api/keys — mint a key

Requires a login token — you can't mint a key with a key. Up to 10 keys per account; scopes is optional and defaults to read-only. Minting needs a key-enabled account with a verified email.

Auth: Bearer login token only Rate limit: 10 requests/hour per IP
Request
curl -X POST "https://api.trainbadger.app/api/keys" \
  -H "Authorization: Bearer <your-login-token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Claude agent", "scopes": ["delay-repay:read", "delay-repay:write"]}'
Response — 201
{
  "id": 7,
  "name": "Claude agent",
  "key": "tb_live_5b1d3c2a00004000800000000000000000000000000000000000000000000",
  "prefix": "tb_live_5b1d",
  "scopes": ["delay-repay:read", "delay-repay:write"],
  "createdAt": "2026-09-22 10:15:00"
}

key is returned only in this response — save it now. 403 if your account isn't enabled for API keys or its email isn't verified; 400 for an invalid name (1-40 trimmed characters) or scopes; 409 at the 10-key cap.

GET /api/keys — list your keys

Never returns the full key value — just enough to recognise each one.

Auth: Bearer login token only
Request
curl "https://api.trainbadger.app/api/keys" \
  -H "Authorization: Bearer <your-login-token>"
Response — 200
{
  "enabled": true,
  "keys": [
    {
      "id": 7,
      "name": "Claude agent",
      "prefix": "tb_live_5b1d",
      "scopes": ["delay-repay:read", "delay-repay:write"],
      "createdAt": "2026-09-22 10:15:00",
      "lastUsedAt": "2026-09-22 11:02:41"
    }
  ]
}

If your account isn't enabled for API keys, this returns { "enabled": false, "keys": [] } rather than an error.

DELETE /api/keys/:id — revoke a key

Immediate — the key stops authenticating on your very next request. No grace period, no confirmation step beyond the request itself.

Auth: Bearer login token only
Request
curl -X DELETE "https://api.trainbadger.app/api/keys/7" \
  -H "Authorization: Bearer <your-login-token>"

204 on success, 404 for an unknown id or one that isn't yours.