Turkish State Railways Ticket and Connection Scraper avatar

Turkish State Railways Ticket and Connection Scraper

Pricing

from $1.50 / 1,000 connections

Go to Apify Store
Turkish State Railways Ticket and Connection Scraper

Turkish State Railways Ticket and Connection Scraper

Scrape live TCDD train connections, schedules, ticket prices, transfers, delays, and fares across Turkey and Europe. Extract structured TCDD timetable data for travel apps, price monitoring, analytics, and AI agents.

Pricing

from $1.50 / 1,000 connections

Rating

5.0

(3)

Developer

Jindřich Bär

Jindřich Bär

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

TCDD train tickets & connections scraper

Search TCDD (Turkish State Railways / Türkiye Cumhuriyeti Devlet Demiryolları) train connections and ticket prices across Türkiye and its international routes, and get back a structured dataset of routes, times, transfers, seat availability, and fares — ready to plug into spreadsheets, databases, dashboards, or AI agents.

Pulls live data from the official TCDD ticket shop (ebilet.tcddtasimacilik.gov.tr), so you always get the same trains, prices, and schedules a passenger would see when booking online.

What you can do with it

  • Compare ticket prices between stations in real time — Ankara to İstanbul, İzmir to Ankara, Konya to İstanbul, and any other route the TCDD timetable covers (including high-speed YHT services and the international sleeper to Bucharest / Sofia).
  • Check seat availability per cabin class (Business, Economy, wheelchair) before you book.
  • Monitor fares over time by scheduling the actor to run daily / hourly and writing the results to your own datastore.
  • Build a travel-planning assistant — feed the JSON output directly into an LLM agent that answers "what's the cheapest YHT from Ankara to İstanbul tomorrow morning" or "is there a direct high-speed train from Konya to İstanbul today."

Typical use cases: travel comparison sites, price-monitoring tools, business-trip planners, train spotters, journalists working on transport coverage, and AI agents that need a structured rail-data source for Türkiye.

Input

FieldRequiredDefaultDescription
fromyesDeparture station (e.g. "ANKARA GAR", "İSTANBUL(PENDİK)", "İZMİR")
toyesDestination station
datenotodayDeparture date (date picker, ISO YYYY-MM-DD)
timenonowDeparture time (HH:MM, 24-hour, Istanbul local time)
adultsno1Number of adult passengers (1–6)
maxResultsno20Maximum number of connections to push to the dataset (1–200)

Station names are matched against the official TCDD list with diacritics ignored and a fuzzy fallback, so Istanbul finds İSTANBUL(…) and Tabriz finds TEBRİZ. Matching tries, in order: exact, exact word, prefix, substring, then closest edit-distance. When a name matches several stations (there are five İstanbul stations), the actor prefers the pair that the timetable marks as directly connected — e.g. it picks İSTANBUL(HALKALI) for an international route. It also auto-detects whether the route is DOMESTIC or INTERNATIONAL from the stations' countries.

How the search works

The TCDD timetable API only returns direct trains for a single day and cannot build itineraries with transfers. To collect up to maxResults connections the actor pages forward day by day from your departure date, de-duplicating trains that straddle a day boundary, and stops as soon as it hits any of: maxResults connections, ten days searched, two consecutive empty days, or a ~75-second time budget. Run progress is logged per day (19-06: +1 (3/20)), so you can watch it fill up.

Two consequences worth knowing:

  • Sparse / international routes return fewer results and take longer. International searches are slow server-side (~20 seconds each), and routes like the daily İstanbul–Sofia sleeper only run one train a day — so a request for maxResults: 20 typically returns ~4 connections and stops at the time budget rather than paging for minutes. Dense domestic routes (e.g. Ankara–İstanbul YHT) fill up in one or two fast requests.
  • No direct train ⇒ 0 results. If no direct service runs the route on the searched days, the run finishes cleanly with an empty dataset (and a warning if the stations aren't listed as a valid pair), rather than erroring.

Example input

{
"from": "ANKARA GAR",
"to": "İSTANBUL(PENDİK)",
"date": "2026-07-14",
"time": "08:00",
"adults": 1,
"maxResults": 25
}

Output

Each item in the dataset describes one train connection.

Example item

{
"id": "81001-15072026",
"from": "ANKARA GAR",
"to": "İSTANBUL(PENDİK)",
"fromId": "98",
"toId": "48",
"departure": "2026-07-15T05:55:00+03:00",
"arrival": "2026-07-15T09:40:00+03:00",
"durationMinutes": 225,
"transfers": 0,
"priceAmount": 930,
"priceCurrency": "TRY",
"trainTypes": ["YHT"],
"requiresTransfer": false,
"legs": [
{
"trainId": 172246,
"trainNumber": "81001-15072026",
"trainName": "81001 ANKARA - İSTANBUL",
"commercialName": "YHT ANKARA - İSTANBUL",
"type": "YHT",
"from": "ANKARA GAR",
"to": "İSTANBUL(PENDİK)",
"fromId": "98",
"toId": "48",
"departure": "2026-07-15T05:55:00+03:00",
"arrival": "2026-07-15T09:40:00+03:00",
"durationMinutes": 225,
"distanceKm": 512.666,
"priceAmount": 930,
"priceCurrency": "TRY",
"cabinClasses": [
{ "code": "C", "name": "BUSİNESS", "availabilityCount": 48, "minPrice": 1395, "minPriceCurrency": "TRY" },
{ "code": "Y1", "name": "EKONOMİ", "availabilityCount": 346, "minPrice": 930, "minPriceCurrency": "TRY" }
]
}
]
}

Field reference

FieldTypeDescription
idstringConnection identifier built from the train number(s) ("81001-15072026")
from / tostringStation names
fromId / toIdstringTCDD station IDs
departure / arrivalISO 8601 datetimeScheduled time in Istanbul local time (+03:00)
durationMinutesintegerTotal travel time
transfersintegerNumber of transfers (0 = direct)
priceAmountnumber or nullCheapest fare for the whole connection. Null when no bookable offer is available
priceCurrencystring or nullISO 4217 ("TRY")
trainTypesarrayDistinct train categories across the legs, e.g. ["YHT"] (high-speed), ["AH"] (mainline)
requiresTransferbooleantrue when the connection requires changing trains
legs[]arrayPer-leg breakdown: train number/name, type, departure/arrival, distance, fare, and per-cabin-class seat availability

cabinClasses[] on each leg lists every bookable cabin class with its remaining seat count (availabilityCount) and cheapest fare (minPrice / minPriceCurrency).

Pricing

Pay-per-event — one search-result event is charged for each connection pushed to the dataset. A query with maxResults: 10 charges for at most ten events, regardless of how many requests 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~tcdd-ticket-scraper/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"from": "ANKARA GAR", "to": "İSTANBUL(PENDİK)", "maxResults": 10}'

Or run asynchronously and poll for status / dataset items:

# Start a run
curl -X POST "https://api.apify.com/v2/acts/jindrich.bar~tcdd-ticket-scraper/runs?token=<APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"from": "ANKARA GAR", "to": "İSTANBUL(PENDİK)"}'
# When it's done, read the dataset
curl "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 YHT fare changes for a fixed Ankara → İstanbul route.
  • Hourly refresh for a holiday week — keep a real-time price board for a popular route.
  • Weekly market scan — compare a basket of intercity routes for week-over-week price changes.

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 TCDD 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 YHT from Ankara to İstanbul tomorrow morning?"
  • "Find me a direct high-speed train from Konya to İstanbul on 2026-07-14."
  • "How long does the train from İzmir to Ankara take, and when does the next one leave?"
  • "Is there an international sleeper from İstanbul to Bucharest in the next two weeks?"

Troubleshooting & support

Most issues come from station naming, date/time formatting, or sparsely-served routes.

Common problems

No results returned

  • The departure date is in the past, or beyond the TCDD timetable horizon. Pick a date within the next months.
  • There's no direct train on the searched days — the API can't build transfer itineraries, so such routes return an empty dataset by design. The actor logs a warning when the destination isn't listed as reachable from the origin.

Fewer results than maxResults (or the run takes a while)

  • This is expected on sparse and international routes: the actor pages one day per request, international requests are ~20 seconds each, and some routes run only one train a day. It stops at a ~75-second time budget, so you may get only a handful of connections. Dense domestic routes fill up quickly. See How the search works above.

No station found for "…" error

  • Station names are matched against the official TCDD list with diacritics ignored and a fuzzy fallback, so "istanbul", "Ankara", or a small typo usually resolves. If it still fails, try the name closer to how TCDD spells it (e.g. "ANKARA GAR", "İSTANBUL(PENDİK)", "İZMİR (BASMANE)").

Times look off by a few hours

  • All times are returned in Istanbul local time (+03:00). Turkey observes a fixed UTC+3 with no daylight saving.

Different prices than the TCDD shop shows in a browser

  • The actor returns the cheapest available fare per cabin class. Member discounts, promo codes, and reduced passenger types (student, senior, child) aren't applied — those require a logged-in account or a specific passenger type.

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), and the expected vs. actual output.

Need a different data source?

If you're scraping connections across multiple operators, check our companion actors:

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.