# Flight Route Timetable Scraper (`apt_marble/flight-route-timetable-scraper`) Actor

Get the published timetable for any airport pair. One row per scheduled flight: airline, flight number, aircraft type, local departure and arrival times, block time, weekday pattern and the dates each schedule runs. Filter by airline, season or frequency. No account or login needed.

- **URL**: https://apify.com/apt\_marble/flight-route-timetable-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 scheduled flight founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Flight Route Timetable Scraper

Give it an airport pair and get the published timetable back as a clean table — one row for
every scheduled flight, with the airline, flight number, aircraft type, departure and arrival
times, block time, the days of the week it operates and the dates each schedule is valid for.
It is built for airline network planners, aviation consultancies, schedule-data teams, travel
products and anyone who needs to know exactly what flies between two airports and when, in a
form they can load straight into a model or a spreadsheet.

### What you can do with it

- **Map a competitor's schedule on a route** — every flight number an airline publishes
  between two airports, with the aircraft it plans to use and the weeks each timing runs.
- **Build a schedule dataset for a market** — feed in a list of airport pairs and get one
  consistent table covering all of them, both directions if you add both.
- **Find the capacity picture behind a route** — how many flights a week, which days, which
  aircraft types, how many cabins are sold.
- **Track seasonal service** — which airlines only fly a route in some months, and the exact
  dates each seasonal timing starts and stops.
- **Feed connection and rotation modelling** — local departure and arrival times, the day
  marker for a flight that lands the next day, block time in minutes and the great-circle distance.
- **Keep a schedule history** — schedule it to run daily or weekly and keep the results, so you
  can see when a timing, an aircraft type or a frequency changed.

### What you get

One row per scheduled flight. A real record, abridged:

```json
{
  "routeId": "JFK-LHR",
  "originIata": "JFK",
  "originName": "New York City (JFK)",
  "originCity": "New York",
  "originCountry": "US",
  "destinationIata": "LHR",
  "destinationName": "London Heathrow (LHR)",
  "destinationCity": "London",
  "destinationCountry": "GB",
  "airline": "Virgin Atlantic",
  "airlineIata": "VS",
  "flightNumber": "VS 26",
  "aircraft": "Airbus A330-900neo",
  "departureTimeLocal": "08:00:00",
  "arrivalTimeLocal": "20:10:00",
  "arrivalDaysLater": 0,
  "blockTimeMin": 430,
  "originTimeZone": "America/New_York",
  "destinationTimeZone": "Europe/London",
  "validFrom": "2026-11-01",
  "validTo": "2027-03-13",
  "validToOpenEnded": false,
  "validityHasGaps": false,
  "validityPeriods": [
    { "validFrom": "2026-11-01", "validTo": "2027-03-13", "daysPerWeek": 7 }
  ],
  "routeIsComplete": true,
  "operatesMon": true,
  "operatesSat": true,
  "daysOfWeek": ["sun", "mon", "tue", "wed", "thu", "fri", "sat"],
  "daysPerWeek": 7,
  "cabinClassCount": 3,
  "publishedDurationMin": 440,
  "distanceMi": 3440,
  "distanceKm": 5536.1,
  "routeIsSeasonal": false,
  "collectedAt": "2026-08-16T09:14:00.000Z"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `routes` | list of text | — (required) | The airport pairs you want, one per line: `JFK-LHR`. `JFK > LHR`, `JFK to LHR` and `JFK LHR` also work. Timetables are directional, so add both `JFK-LHR` and `LHR-JFK` for both directions. City-wide codes such as NYC or LON are not accepted — use the individual airports. |
| `airlines` | list of text | empty | Keep only these airlines. Two-letter codes (`BA`, `VS`) match the airline code exactly, never a word inside another airline's name; longer text (`Virgin`, `British Airways`) matches the name. Empty means every airline on the route. |
| `rowGranularity` | select | `schedule` | `One row per scheduled flight` merges the published periods of the same timing into one line — the usual choice — and each merged line lists the periods it merged and says whether they run back to back. `One row per validity period` keeps every period separately, for exact week-by-week work. |
| `language` | select | `en` | The language used for airport names, e.g. German gives `München (MUC)`. Airline and aircraft names are published in English whichever you pick. |
| `yearFrom` | integer | empty | Keep only schedules running in this year or later. |
| `yearTo` | integer | empty | Keep only schedules starting in this year or earlier. |
| `minDaysPerWeek` | integer | `0` | Keep only flights operating on at least this many days a week within a single published period. Set `7` for daily services only — a flight that runs six days in one period and five in another does not qualify. |
| `includeSeasonalOnly` | true/false | `false` | Keep only airlines whose service on the route runs in some months rather than all year. |
| `includeCodeshares` | true/false | `true` | Turn off to be certain a flight sold under a partner airline's number can never appear. |
| `maxFlightsPerRoute` | integer | `500` | Ceiling on flights returned for any one route. Busy routes publish several hundred. |
| `maxRoutes` | integer | `50` | Safety ceiling on how many routes one run reads. |
| `parallelRoutes` | integer | `6` | How many routes to work on at once, 1–8. |

### Output fields

| Field | Description |
| --- | --- |
| `routeId` | The pair this row belongs to, e.g. `JFK-LHR`. |
| `originIata`, `destinationIata` | Airport codes, in the direction flown. |
| `originName`, `destinationName` | Airport names, in the language you chose. |
| `originCity`, `originCountry`, `destinationCity`, `destinationCountry` | Where each airport is. |
| `airline`, `airlineIata` | The airline publishing the flight. |
| `flightNumber`, `flightNumberDigits` | The flight number, tidied, and its numeric part on its own. |
| `flightNumberMatchesAirline` | `true` when the flight number belongs to the airline it is listed under. |
| `aircraft`, `aircraftId` | The aircraft type planned for the flight. |
| `departureTimeLocal`, `arrivalTimeLocal` | Scheduled times, local to each airport. |
| `arrivalDaysLater` | `1` when the flight lands the next day, `2` for two days later, `0` otherwise. |
| `blockTimeMin` | Gate-to-gate time in minutes, worked out across the two time zones rather than by subtracting clocks. |
| `originTimeZone`, `destinationTimeZone` | The time zone each local time belongs to. |
| `validFrom`, `validTo` | The outer dates this schedule runs between. On a merged row this is the earliest start and the latest end of the periods it merged, which is not the same as service on every day in between — see `validityHasGaps`. |
| `validToOpenEnded` | `true` when the source published no end date; `validTo` is then empty rather than guessed. |
| `validityBandCount` | How many published periods were merged into this row. |
| `validityPeriods` | Every one of those periods exactly as published — start, end and the weekdays it runs — so you can count the schedule yourself instead of reading the outer window as continuous. |
| `validityHasGaps` | `true` when there is at least one day inside the outer window on which the source publishes no service for this flight, `false` when the periods run back to back, empty when a missing date makes it impossible to tell. |
| `daysOfWeekAreUnionOfBands` | `true` when the weekday pattern covers several merged periods rather than a single one. |
| `operatesMon` … `operatesSun` | Whether the flight operates on each weekday. On a merged row this is true if it operates that day in any of the merged periods. Empty when the source published no weekday pattern at all. |
| `daysOfWeek`, `daysPerWeek` | The same pattern as a list, and how many days a week that is. |
| `maxDaysPerWeekInAnyPeriod` | The most days a week the flight runs in any single published period. This is what the minimum-days-a-week filter is judged on, because the merged figure above can be higher than any period on its own. |
| `routeIsComplete` | `true` when every airline on the route was collected for this run. `false` means some of the route's airlines could not be read, so the route's table is partial and says so. |
| `airlinesReadOnRoute`, `airlinesUnreadableOnRoute` | How many of the route's airlines were collected, and how many were not. |
| `cabinClassCount`, `cabinClassPattern` | How many cabins are sold on the flight, and the published pattern. Cabin names are not published, so they are never guessed. |
| `publishedDurationMin` | The typical journey time published for the route. |
| `distanceMi`, `distanceKm` | Route distance in statute miles and in kilometres. |
| `routeIsSeasonal`, `routeMonths` | Whether the airline's service on the route is seasonal, and the months it runs. Empty when nothing was published. |
| `isSharedSchedule`, `sharedWithFlightNumbers` | Flags the rare case where two airlines publish the same timing, without guessing which one operates it. |
| `language` | The language your airport names came back in. |
| `collectedAt` | When the row was collected. |
| `flags` | Short notes on anything that qualifies the row, e.g. that a merged row's weekdays cover several periods. |

### Pricing

You pay per result, plus a small amount for each route the actor works through.

| What you pay for | Price |
| --- | --- |
| Scheduled flight found — one row in your results | $1.80 per 1,000 |
| Route timetabled — one airport pair taken all the way through | $0.015 each |

Working through a route means resolving both airports, finding every airline that flies the
pair and collecting each of their published timetables. That happens whether the route turns
out to have two flights or two hundred, and a route with no published service is charged too,
because the work was still done — you get a clear "no service published" answer instead of
silence. A route the source could not complete is not charged at all: if some of its airlines
could not be read and the rest published nothing, you are told the route failed, and you pay
nothing for it.

**Worked example.** Ten transatlantic pairs, averaging 40 scheduled flights each:
400 rows × $0.0018 = **$0.72**, plus 10 routes × $0.015 = **$0.15**. Total **$0.87**.

A single busy route such as JFK–LHR returns around 170 flights across five airlines for about
**$0.32**.

### Limits & what this actor cannot do

- **Schedules are a snapshot at the moment of collection.** Airlines refile schedules
  constantly; a timing, an aircraft type or a frequency can change after the row was written.
- **It reports what the source publishes to the public.** It does not check seat availability,
  does not hold or book anything, and it does not include fares.
- **Non-stop service only.** These are published timetables for an airport pair, not
  connecting itineraries. Two airports with no published non-stop service are reported as
  having none — which is an answer, not an error.
- **Routes and schedules are seasonal.** A route can be genuinely out of season on the dates
  you care about; that is reported as no published service rather than as a failure.
- **A route the source refuses to complete is reported as a failed route, never as "no
  flights".** That holds airline by airline: if some of a route's airlines could not be read
  and the rest published nothing, the route is failed and is not charged — it is never
  reported as a route with no service. Failed routes are counted separately in the run
  summary, and a run where nothing could be read ends as a failed run instead of a green run
  with an empty table.
- **A partly collected route says so on every row.** If one airline of five could not be read,
  the rows you get carry `routeIsComplete: false` with the counts, and the route is listed
  under partial routes in the run summary. You are never left comparing a one-airline table
  against a five-airline one without knowing.
- **Timetables are directional.** `JFK-LHR` does not include the return; add `LHR-JFK` as well.
- **Not every airport is carried.** Smaller fields may not be listed, and city-wide codes such
  as NYC or LON are rejected with the individual airports named instead of being guessed at.
- **Aircraft type is the type planned for the flight**, which can differ from the aircraft that
  actually operates on the day.
- **How many cabins are sold is published; what those cabins are called is not**, so cabin
  names are left empty rather than invented.
- **Flights are listed under the airline that operates them.** A flight marketed by a partner
  airline under its own number is usually not listed separately.
- **Airport names follow the language you choose; airline and aircraft names are published in
  English** whichever language you pick.
- **Merged rows describe a whole window, and that window can have holes in it.** With one row
  per scheduled flight, the weekday pattern and block time cover the merged periods and each
  row says so. A schedule that runs in October and again in March is one row spanning both,
  with `validityHasGaps: true` and every period listed in `validityPeriods` — read those, not
  the outer dates, if you are counting departures. Choose one row per validity period when you
  want every period as its own line.
- **A per-route ceiling keeps the earliest departures.** If a route publishes more flights than
  your ceiling allows, the day's earliest departures are kept and the number left behind is
  stated in the run summary — nothing is dropped without telling you.
- **Speed depends on the size of the job and on how quickly the source answers.** No fixed
  throughput is promised.
- **The source's terms govern automated access.** You are responsible for using the results
  lawfully and in line with those terms.

### FAQ

**Do I need an account on the source site?**
No. Nothing is signed into and nothing is set up — you fill in the routes and run it.

**Does it need my login or password?**
No. It never asks for credentials of any kind.

**Can I schedule it?**
Yes. Run it daily or weekly and keep each result to build a schedule history, which is the
only way to see when an airline quietly changed a timing or swapped an aircraft type.

**Are these flights bookable?**
They are published schedules, not live availability. A flight in your results is a service the
airline has filed; whether a seat is for sale on a given date, and at what price, is a
different question this actor does not answer.

**What happens if a route can't be read?**
It is reported as a failed route and counted separately from routes that genuinely have no
service. That distinction is the point of the run summary: you always know whether an empty
result means "nothing flies here" or "this one did not come back".

**Why do I sometimes get several rows with the same flight number?**
Because the airline publishes the same flight number with different timings or a different
aircraft across the year — for example a winter timing and a summer one. Each distinct
schedule is its own row, with its own valid-from and valid-to dates.

**Can I get both directions at once?**
Add both pairs, for example `JFK-LHR` and `LHR-JFK`. Each direction is its own timetable and
they routinely differ in frequency, aircraft and timings.

# Actor input Schema

## `routes` (type: `array`):

The airport pairs you want timetables for, one per line, written as two airport codes: "JFK-LHR". "JFK > LHR", "JFK to LHR" and "JFK LHR" all work too. Timetables are directional, so add both "JFK-LHR" and "LHR-JFK" if you want each direction. City-wide codes such as NYC or LON are not accepted here — use the individual airports.

## `airlines` (type: `array`):

Limit the timetable to certain airlines. Use two-letter airline codes (BA, AA, VS), which are matched exactly, or names ("British Airways", "Virgin"), which are matched anywhere in the airline's name. Leave empty to get every airline that flies the route.

## `rowGranularity` (type: `string`):

"One row per scheduled flight" gives you one line for each flight number and timing, with the validity dates widened to cover every period it runs — the usual choice. Each merged line lists those periods and states whether they run back to back, so a schedule that stops for the winter is never read as continuous service. "One row per validity period" keeps every published period separately, which is what you want if you need the exact week-by-week pattern.

## `language` (type: `string`):

The language used for airport names in your results — for example German gives "München (MUC)" instead of "Munich (MUC)". Airline and aircraft names are published in English only, whichever language you choose.

## `yearFrom` (type: `integer`):

Keep only flights whose published schedule runs in this year or later. Leave empty to keep everything the source publishes.

## `yearTo` (type: `integer`):

Keep only flights whose published schedule starts in this year or earlier. Leave empty to keep everything the source publishes.

## `minDaysPerWeek` (type: `integer`):

Keep only flights that operate on at least this many days of the week within a single published period. Set it to 7 for daily services only — a flight running six days in one period and five in another does not count as daily — or leave it at 0 to keep everything.

## `includeSeasonalOnly` (type: `boolean`):

Keep only airlines whose service on the route is marked as running in some months of the year rather than all year round. Useful for summer- and winter-season planning.

## `includeCodeshares` (type: `boolean`):

The source lists each flight under the airline that operates it, so this normally changes nothing. Turn it off if you want to be certain that a flight sold under a partner airline's number can never appear in your results.

## `maxFlightsPerRoute` (type: `integer`):

A ceiling on how many scheduled flights are returned for any one route. Busy routes such as London to New York publish several hundred.

## `maxRoutes` (type: `integer`):

A safety ceiling on how many routes one run will read, so a long list cannot run away with your budget.

## `parallelRoutes` (type: `integer`):

How many routes to work on at once. The default is a good balance between speed and being a polite visitor to the source.

## Actor input object example

```json
{
  "routes": [
    "JFK-LHR",
    "LHR-JFK",
    "JFK-LGW"
  ],
  "airlines": [
    "BA",
    "Virgin Atlantic"
  ],
  "rowGranularity": "schedule",
  "language": "en",
  "yearFrom": 2026,
  "yearTo": 2027,
  "minDaysPerWeek": 0,
  "includeSeasonalOnly": false,
  "includeCodeshares": true,
  "maxFlightsPerRoute": 500,
  "maxRoutes": 50,
  "parallelRoutes": 6
}
```

# Actor output Schema

## `flights` (type: `string`):

One row per scheduled flight: airline, flight number, aircraft, times, weekday pattern and validity dates.

## `overview` (type: `string`):

The same flights as a table, earliest departure first.

## `spreadsheet` (type: `string`):

All scheduled flights in one CSV file.

## `runSummary` (type: `string`):

Counts for this run: flights collected, routes read, routes with no published service, and routes that could not be completed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "routes": [
        "JFK-LHR"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/flight-route-timetable-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "routes": ["JFK-LHR"] }

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/flight-route-timetable-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "routes": [
    "JFK-LHR"
  ]
}' |
apify call apt_marble/flight-route-timetable-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/flight-route-timetable-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/TzLRBIiiH7ipQgwkV/builds/e1BMmbntgSXPj5KRp/openapi.json
