National Rail Train Ticket & Connection Scraper
Pricing
from $0.40 / 1,000 trips
National Rail Train Ticket & Connection Scraper
Scrape live train connections, schedules, ticket prices, transfers, delays, and fares across the UK. Extract structured UK timetable data for travel apps, price monitoring, analytics, and AI agents.
Pricing
from $0.40 / 1,000 trips
Rating
5.0
(3)
Developer
Jindřich Bär
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
0
Monthly active users
a day ago
Last modified
Categories
Share
National Rail (UK) train tickets & connections scraper
Search National Rail train connections and ticket prices across England, Scotland, and Wales, and get back a structured dataset of routes, times, changes, and fares — ready to plug into spreadsheets, databases, dashboards, or AI agents.
Pulls live data from the official National Rail journey planner, so you always get the same trains, prices, and schedules a passenger would see on nationalrail.co.uk or in the National Rail app.
What you can do with it
- Compare ticket prices between any two UK stations in real time — London to Edinburgh, Manchester to Birmingham, Cardiff to Bristol, and every other route the National Rail timetable covers (LNER, Avanti West Coast, GWR, CrossCountry, ScotRail, Northern, and all other TOCs).
- Get every fare, not just the headline price — Advance, Off-Peak, Anytime, Standard and First Class are all returned with their ticket type and route restriction, so you can see exactly which ticket produced the cheapest result.
- Find direct trains or allow changes, with an optional extra buffer at every interchange.
- Search by arrival time — "get me there by 09:00" — not just by departure.
- Monitor fares over time by scheduling the actor to run daily / hourly and writing the results to your own datastore. Advance fares move a lot as departure approaches.
- Build a travel-planning assistant — feed the JSON output directly into an LLM agent that answers "what's the cheapest train from London to Manchester next Tuesday morning."
- Track delays and journey duration — every leg includes scheduled times, real-time times, platforms, and the current delay in minutes.
Typical use cases: travel comparison sites, price-monitoring tools, business-trip planners, rail enthusiasts, journalists working on transport coverage, and AI agents that need a structured rail-data source for Great Britain.
Input
| Field | Required | Default | Description |
|---|---|---|---|
from | yes | — | Origin station name or CRS code (e.g. "London Kings Cross", "KGX", or a group like "London") |
to | yes | — | Destination station name or CRS code |
date | no | today | Travel date (date picker, ISO YYYY-MM-DD), UK local time |
time | no | now | Travel time (HH:MM, 24-hour), UK local time |
timeType | no | "DEPART" | "DEPART" treats the time as a departure time, "ARRIVE" as a required arrival time |
adults | no | 1 | Adult passengers (16+) used for fare pricing |
children | no | 0 | Child passengers (5–15) used for fare pricing |
fareClass | no | "ANY" | "ANY", "STANDARD", or "FIRST" — which ticket classes to price |
directTrains | no | false | Only return journeys with no change of train |
reducedTransferTime | no | false | Allow tighter connections than the default minimum interchange time |
increasedInterchange | no | "ZERO" | Extra buffer at every change: "ZERO", "THIRTY_MINUTES", or "SIXTY_MINUTES" |
onlySearchForSleeper | no | false | Restrict to overnight sleeper services (Caledonian Sleeper, Night Riviera) |
overtakenTrains | no | true | Include slower services that a later, faster train overtakes |
useAlternativeServices | no | false | Let the planner include alternative (replacement or diverted) services |
includeAllFares | no | false | Store every fare offered in fares[], not just the cheapest ones |
maxResults | no | 20 | Maximum number of journeys to push to the dataset |
Station names are matched against the official National Rail station list, which is bundled with
the actor and refreshed on every build — so no lookup request is made at run time. You can pass a
full name ("Manchester Piccadilly"), a partial name ("Kings Cross"), a CRS code ("MAN"), or a
group name ("London", "Birmingham", "Glasgow"), which searches all stations in that city. The
actor paginates the journey planner automatically — it returns about five journeys per request — so
you reliably get the number of connections you asked for.
Example input
{"from": "London Kings Cross","to": "Edinburgh","date": "2026-09-15","time": "08:00","adults": 1,"fareClass": "ANY","maxResults": 20}
Output
Each item in the dataset describes one train journey, with its legs and the cheapest fares found for it.
Example item
{"id": "bb796bda-2026-09-15T08:03:00+01:00","from": "London Kings Cross","to": "Edinburgh","fromCrs": "KGX","toCrs": "EDB","departure": "2026-09-15T08:03:00+01:00","arrival": "2026-09-15T12:30:00+01:00","realtimeDeparture": null,"realtimeArrival": null,"durationMinutes": 267,"durationText": "4h 27m","transfers": 0,"status": "NORMAL","delayMinutes": 0,"isOvertaken": false,"operators": ["London North Eastern Railway"],"priceAmount": 63,"priceCurrency": "GBP","cheapestFare": {"priceAmount": 63,"priceCurrency": "GBP","fareClass": "STANDARD","fareCategory": "ADVANCE","typeCode": "BSU","typeDescription": "Advance Single","ticketRestriction": "GC","routeCode": "00430","from": "London Kings Cross","to": "Edinburgh","startLegId": 1,"endLegId": 1},"cheapestStandardFare": { "priceAmount": 63, "fareClass": "STANDARD", "typeDescription": "Advance Single" },"cheapestFirstFare": { "priceAmount": 131.7, "fareClass": "FIRST", "typeDescription": "Advance Single (1st Class)" },"legs": [{"legId": 1,"mode": "TRAIN","operator": "London North Eastern Railway","operatorCode": "GR","trainUid": "G80205","trainTripId": "G80205|15/09/2026","from": "London Kings Cross","to": "Edinburgh","fromCrs": "KGX","toCrs": "EDB","departure": "2026-09-15T08:03:00+01:00","arrival": "2026-09-15T12:30:00+01:00","realtimeDeparture": null,"realtimeArrival": null,"delayMinutes": 0,"durationMinutes": 267,"fromPlatform": "4","toPlatform": "6","status": "NORMAL","isReplacementBus": false,"cancelled": false}]}
Field reference
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier for the journey (deduplication-safe across pages and runs) |
from / to | string | Station names |
fromCrs / toCrs | string | CRS codes (KGX, EDB, …) — the standard three-letter UK station codes |
departure / arrival | ISO 8601 with timezone offset | Scheduled times, in UK local time (+01:00 in summer, +00:00 in winter) |
realtimeDeparture / realtimeArrival | ISO 8601 or null | Real-time times, published only close to departure |
durationMinutes | integer | Total travel time |
durationText | string | Duration as National Rail formats it (e.g. "4h 27m") |
transfers | integer | Number of changes (0 = direct) |
status | string | Journey status ("NORMAL", "CANCELLED", …) |
delayMinutes | integer or null | Current delay |
isOvertaken | boolean | True when a later, faster service overtakes this journey |
operators | string[] | Train operating companies serving the journey |
priceAmount | number or null | Cheapest fare of any class, in GBP. Null when no fare is offered |
priceCurrency | string or null | Always "GBP" when a price is present |
cheapestFare | object or null | The fare behind priceAmount, with its class, category, ticket type, and route restriction |
cheapestStandardFare / cheapestFirstFare | object or null | Cheapest fare in each class, so you can compare without re-querying |
fares[] | array | Every fare offered, cheapest first. Only present when includeAllFares is enabled |
legs[] | array | Per-leg breakdown: operator, train ID, board/alight stations and CRS codes, platforms, scheduled and real-time times, delay, cancellation flag |
Prices are converted from the pence the API returns into pounds, so 6300 is reported as 63.
priceAmount is null when National Rail doesn't offer a bookable through-fare for the journey —
this happens on some connections involving replacement buses or partner-operator-only tickets. The
journey is still returned with full timing and leg detail; only the price is missing.
Pricing
Pay-per-event — one search-result event is charged for each journey pushed to the dataset. That
means a query with maxResults: 10 charges for at most ten events, regardless of how many journey
planner calls the actor makes under the hood.
Using the API
Trigger runs from your own code via the Apify API. With your Apify API token, a POST request runs
the actor synchronously and returns the dataset items:
curl -X POST "https://api.apify.com/v2/acts/jindrich.bar~national-rail-ticket-scraper/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"from": "London Kings Cross", "to": "Edinburgh", "maxResults": 10}'
Or run asynchronously and poll for status / dataset items:
# Start a runcurl -X POST "https://api.apify.com/v2/acts/jindrich.bar~national-rail-ticket-scraper/runs?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"from": "London Kings Cross", "to": "Edinburgh"}'# When it's done, read the datasetcurl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<APIFY_TOKEN>"
Official client libraries are available for JavaScript / TypeScript, Python, and via the Apify REST API directly.
Scheduling
Run the actor on a cron schedule from the Schedules tab in the Apify console — daily, hourly, or any custom cron expression. Common patterns:
- Daily price snapshot at 09:00 — track how Advance fares on a fixed London → Edinburgh route move as the departure date approaches.
- Hourly refresh during a peak week — keep a live price board for a route over a bank holiday or a major event.
- Weekly market scan — compare a basket of intercity routes (
London↔Manchester,London↔Leeds,Birmingham↔Glasgow) for week-over-week price changes.
Schedules can fan out into multiple datasets, push to a webhook, or trigger downstream actors when the run finishes.
Use with AI Agents (Apify MCP)
This actor is exposed through the Apify Model Context Protocol (MCP) server, so any AI agent that speaks MCP — Claude, ChatGPT custom agents, OpenAI Agents SDK, Cursor, etc. — can call it directly to fetch live National Rail ticket prices and connection options.
Once the Apify MCP server is connected, the agent picks up the actor's input schema automatically. Typical prompts that work out of the box:
- "What's the cheapest train from London to Edinburgh next Tuesday morning?"
- "Find me a direct train from Manchester Piccadilly to London Euston on 2026-09-15."
- "I need to be in Birmingham by 09:00 from Bristol — what should I catch?"
- "List the next five trains from Leeds to York under £15."
- "How much more is First Class on the 08:03 from Kings Cross to Edinburgh?"
The agent fills the input, runs the actor, and reads the structured dataset items back — no scraping, no HTML parsing, no scheduling logic on the agent side.
Why this works well for agents
- Typed input schema — every field has a title, type, default, and validation rules, so an agent can call the actor without trial-and-error prompting.
- Typed output —
departure,arrival,durationMinutes,transfers,priceAmountare all numbers / ISO timestamps, ready to be diffed, sorted, or compared directly. - Fares are pre-summarised —
cheapestFare,cheapestStandardFare, andcheapestFirstFaremean the agent doesn't have to reason over dozens of raw ticket types to answer "how much?". - Predictable pagination — results come back in chronological order with duplicates removed, so the agent doesn't have to reason about paging.
- Pay-per-event cost control — an agent that asks for five connections pays for five.
Troubleshooting & support
Most issues come from date/time formatting, station naming, or how far ahead you're searching. Try the fixes below before opening an issue.
Common problems
No results returned (reason: "no-results")
The run's status message spells out what was searched and which of your filters could have excluded everything — read that first. The usual causes:
- The travel date is in the past, or more than ~120 days ahead. National Rail publishes timetables and fares roughly 12 weeks out; beyond that the planner has nothing to return.
- The route genuinely has no service at that time (e.g. late-night regional lines, or Sunday
engineering work). Try a different time of day, or turn off
directTrains. directTrainsis enabled on a route with no through service (e.g. Oxford → Oxenholme Lake District), so every journey needs a change. The planner rejects such a search outright.onlySearchForSleeperis enabled on a route with no sleeper service — only the Caledonian Sleeper and Night Riviera qualify.
The run failed (reason: "error")
The actor fails loudly rather than finishing with an empty dataset. The status message and the log carry the reason — an unknown station name (with suggestions), an unreachable journey planner, or an HTTP error from National Rail. Re-running usually fixes transient network and 5xx errors.
The wrong station was picked
- Station names are resolved to the closest match: exact CRS code first, then exact name, then a
name that starts with your query, then one that contains it.
"London"resolves to the London group (all London terminals), not to a specific station. - If nothing matches the whole query, trailing words are dropped and the search retried — National
Rail's catalogue omits suffixes that people use in conversation, so
"Edinburgh Waverley"becomesEdinburgh(EDB) and"York Station"becomesYork(YRK). This is logged as a warning so you can see which station was actually searched. - If you need a specific terminal, pass its CRS code (
EUSfor Euston,KGXfor Kings Cross,PADfor Paddington) or its full name. Codes are unambiguous and always win.
priceAmount is null on some journeys
- National Rail doesn't offer a bookable through-fare for that connection — common where a leg is a replacement bus, or where tickets are sold only by a partner operator. Timing and leg data are still complete.
Fewer results than maxResults
- The actor pages forward from the last departure it saw and stops when the planner returns nothing
new. On a sparsely served route (small stations, late evening), the timetable simply doesn't have
more trains that day. Try an earlier
time, or a larger nearby hub as the origin.
Invalid date or Invalid time error
datemust beYYYY-MM-DD(e.g.2026-09-15).timemust beHH:MMin 24-hour format (e.g.08:00, not8 AM). Both are optional — leave them empty to use "now".
Different prices than nationalrail.co.uk shows
- The actor returns the cheapest fare available for that specific journey at the moment it runs. Advance fare quotas sell out continuously, so a price can change within minutes.
- Railcard discounts are not applied — all fares are quoted undiscounted. Split-ticketing and operator-specific promotions aren't applied either.
Delays show as null
- Real-time data is only published a few hours before departure. For journeys further out,
realtimeDeparture,realtimeArrival, and per-leg delays staynulluntil live data appears.
FAQs
Can I search by CRS code instead of station name?
Yes — from and to accept both. CRS codes (KGX, MAN, EDB) are matched first and are the
most reliable way to target a specific station. The codes are also returned as fromCrs / toCrs.
What are "group" stations?
National Rail groups all stations in a city under one code — "London", "Birmingham",
"Manchester", "Glasgow". Searching from a group returns the best journey from any station in
that city, which is usually what you want for intercity travel.
Can I search for a return journey? Not in one run — each run searches one direction. Run the actor twice (outbound and return) and merge the datasets. Note that fares are quoted as singles, so two Advance Singles is the normal way to price a return anyway.
Does it cover the Tube, DLR, trams, or buses?
National Rail services only. Underground and DLR stations appear in the station list but are not
useful as journey endpoints. Rail replacement buses do appear as legs, flagged with
isReplacementBus: true.
Can I apply a railcard discount? No — fares are returned undiscounted. Apply your railcard's percentage yourself if you need an estimate (most give 1/3 off, subject to minimum fares).
Can I get seat availability or book a ticket? No — the actor returns timetable and fare data only. Booking requires a retailer's checkout flow.
Support
Open an issue on the actor's Issues tab in the Apify console. Include the full input JSON, the
run ID (visible in the run URL, e.g. console.apify.com/actors/runs/<RUN_ID>), and the expected
vs. actual output — that lets the maintainer pull the exact logs and reproduce the issue quickly.
Need a different data source?
If you're scraping connections across multiple operators, check our companion actors:
- Deutsche Bahn ticket scraper — German and European rail timetable and ticket prices.
- SNCF train ticket scraper — French rail timetable and ticket prices.
- ÖBB ticket scraper — Austrian and Central-European rail timetable and ticket prices.
- Trenitalia connection scraper — Italian rail timetable and ticket prices.
- České dráhy connection scraper — Czech rail timetable and ticket prices.
- TCDD ticket scraper — Turkish State Railways timetable and ticket prices.
- Flixbus connection scraper — long-distance bus routes across Europe and the US.
All these actors emit a comparable schema (from, to, departure, arrival, price, leg-level
breakdown), so an aggregator agent can merge their outputs into a single multi-modal travel search.