Skip to content
Alpha — Headcode is currently in alpha. APIs and data may change without notice.

Historical data

Headcode retains normalised service data for 13 months, so you can look back at what ran on a past day — not just what’s running now. Two capabilities expose this history:

  • Service-by-date discovery — list past services for a date, then drill into any one.
  • Historical station boards — an arrivals/departures board for a past day, with aimed, actual, and last-known expected times.

Both are the read-side counterparts to the live service and board endpoints, and reuse the same response shapes so you can share parsing code.

Pass a date to the service list endpoint without a uid to list the services that ran on that day, optionally filtered:

GET /v1/services?date=2026-05-14&toc=GR&origin=KGX&limit=20
ParameterDescription
dateScheduled start date (YYYY-MM-DD), required
tocTwo-character ATOC operator code
originService origin — a TIPLOC or any station identifier (CRS, etc.)
destinationService destination — a TIPLOC or any station identifier
headcodeFour-character headcode
limit / offsetPagination, default 20, max 100

The response is a paginated envelope. Each row carries the service’s identity plus its origin and destination calling points with scheduled times — enough to display a result and follow up for detail:

{
"services": [
{
"rid": "202605141A45",
"uid": "C12345",
"ssd": "2026-05-14",
"toc_code": "GR",
"calling_points": [
{ "location_type": "OR", "tiploc": "KNGX", "public_departure": "08:00" },
{ "location_type": "DT", "tiploc": "EDINBUR", "public_arrival": "12:20" }
]
}
],
"total": 142,
"limit": 20,
"offset": 0
}

Use the rid to fetch full detail — calling points, formation, associations, actual times — from the unchanged detail endpoint:

GET /v1/services/202605141A45

See Service lookup for more on the detail endpoint.

Past-day boards have their own dedicated endpoints under a /history path segment. date is required:

GET /v1/stations/KGX/history/departures?date=2026-05-14
GET /v1/stations/KGX/history/arrivals?date=2026-05-14
GET /v1/stations/KGX/history/board?date=2026-05-14

The live /departures, /arrivals, and /board endpoints are now purely “now” boards and no longer accept date. The history response shape is identical to the live board, so each row exposes:

  • Aimed times — the scheduled public (scheduled_public) and working (scheduled_working) times.
  • Actual times — the recorded actual time where the service ran.
  • Last-known expected times — the final estimated / working_estimated forecast.
  • Platform, operator, and delay flags.

The same toc, platform, calling_at, and destination filters as the live board apply (the combined /history/board, like the live board, has no calling_at filter).

A history board defaults to the whole day. To narrow it, pass from and to as 24-hour wall-clock times (HH:MM):

ParameterMeaning
fromInclusive window start (HH:MM), defaults to 00:00
toInclusive window end (HH:MM), defaults to end of day
GET /v1/stations/KGX/history/departures?date=2026-05-14&from=08:00&to=10:00

returns departures between 08:00 and 10:00 on that day. A malformed time, or a to earlier than from, returns a 400.

Past-day boards are effectively immutable, so history responses are fully cacheable. Each response carries a Last-Modified, a strong ETag, and Cache-Control: public, max-age=…. Replay the Last-Modified as If-Modified-Since, or the ETag as If-None-Match, and a still-current board returns a bare 304 Not Modified. The data stays revalidatable rather than immutable, so a recent day that later receives corrections is picked up once the max-age expires. (Live boards are unchanged and set no Cache-Control.)

Data is retained for 13 months. A date older than the retention window returns a 400 stating the earliest retained date, rather than an empty success:

{
"status": 400,
"code": "HEADCODE.GENERAL.BAD_REQUEST",
"detail": "Date is outside the retention window; the earliest retained date is 2025-05-12."
}

Headcode keeps each service’s final state: aimed times, actual times, and the last-known expected times. It does not retain how a forecast evolved through the day — the sequence of changing ETAs as Darwin updated them. “Did it run, and was it on time?” is fully answered by the final state; forecast-accuracy or ETA-replay products are out of scope.

This is comparable to TransportAPI’s train/actual_journeys and train/station_actual_journeys endpoints.