# Skyscanner Flight Price Calendar Scraper (`apt_marble/skyscanner-flight-price-calendar-scraper`) Actor

A full year of daily flight prices for any route. Find the cheapest date and the cheapest month, see how prices move across the calendar, and compare the same route across 52 country storefronts where fares genuinely differ. No account or login needed.

- **URL**: https://apify.com/apt\_marble/skyscanner-flight-price-calendar-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.20 / 1,000 price date collecteds

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

## Skyscanner Flight Price Calendar Scraper

Pick a route. Get a full year of departure dates, each with the lowest price Skyscanner publishes for flying that day — the cheapest date, the cheapest date in every month, and where every other date sits against them. Then do the thing nobody else does: collect the same route from several country storefronts at once and see how differently it is priced. Built for travel agencies, affiliate publishers, revenue analysts and anyone who needs to answer "when is this route cheapest?" with data instead of a guess.

### What you can do with it

- **Find the cheapest date to fly** on any route, up to about a year ahead, in one run.
- **Chart a route's seasonality** — cheapest and typical price per month, weekday versus weekend, the shape of the whole year.
- **Compare markets** — collect the same route as travellers in the US, the UK, India or Japan see it. These are genuinely different sets of fares, not one price converted, and the cheapest date can land in a different month.
- **Feed a fare-alert or "best time to book" product** with dated prices you can re-collect on a schedule and compare over time.
- **Price a tour, a package or an ad campaign** around the dates a route is actually cheap.
- **Build route dashboards** for a whole portfolio — a main route plus 20 more, across up to 8 markets, in a single run.

### What you get

One result per departure date, per route, per market. A real result, abridged:

```json
{
  "route": "LOND-NYCA",
  "origin": "LOND",
  "originName": "London",
  "originQuery": "London",
  "originType": "city",
  "destination": "NYCA",
  "destinationName": "New York",
  "destinationQuery": "New York",
  "destinationType": "city",
  "date": "2026-11-29",
  "weekdayName": "Sunday",
  "isWeekend": true,
  "month": "2026-11",
  "daysFromCollection": 105,
  "price": 212.59,
  "hasPrice": true,
  "currency": "GBP",
  "currencyRequested": "GBP",
  "currencyMatchesRequest": true,
  "priceBand": "low",
  "market": "UK",
  "marketName": "United Kingdom",
  "isCheapestDate": true,
  "isCheapestInMonth": true,
  "priceRank": 1,
  "cheapestPriceInMonth": 212.59,
  "medianPriceInMonth": 257.59,
  "percentAboveCheapest": 0,
  "percentVsMedian": -24.52,
  "cheapestPriceInRange": 212.59,
  "cheapestDateInRange": "2026-11-29",
  "isEstimated": true,
  "source": "skyscanner",
  "collectedAt": "2026-08-16T16:23:15.876Z"
}
```

That same route and that same departure date, collected across markets on the same day, came back at 212.59 GBP in the United Kingdom, 287.68 USD in the United States and 45,834 JPY in Japan — and the United States market's own cheapest date was not that date at all, but 2027-01-24 at 242.54 USD. Every result is labelled with the market it came from and the currency that actually came back.

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `origin` | string | `London` | Where the trip starts. An airport code (`LHR`), a city code (`LON`, `NYC`) or a place name (`London`). Codes are used exactly as you type them. |
| `destination` | string | `New York` | Where the trip ends, in the same forms. |
| `additionalRoutes` | array | empty | More routes for the same run, one per line, as `LHR-JFK` or `London > New York`. Up to 20. |
| `placeMatching` | select | `city` | How a place **name** is matched: the whole city and all its airports, or a single airport. |
| `market` | select | `US` | The country storefront to price the route in. This chooses which fares you see, not just the currency. 52 markets available. |
| `currency` | select | match the market | The currency to show prices in. Left alone, each market is priced the way travellers there see it. Every result is labelled with the currency that actually came back. |
| `locale` | select | match the market | The language the market is browsed in. Recorded on every result; it does not change prices. |
| `compareMarkets` | array | empty | Extra storefronts for the same route, one per line: `UK`, or `UK:USD` to fix the currency, or `UK:USD:en-GB` to fix the language too. Up to 7. |
| `startDate` | date | empty | Keep only dates on or after this day. |
| `endDate` | date | empty | Keep only dates on or before this day. |
| `maxDaysPerMarket` | integer | `400` | Ceiling on how many dates are kept per route and market, counting from the earliest. The source publishes about 360, so the default keeps all of them. Maximum 400. |
| `onlyCheapestPerMonth` | boolean | `false` | Compact seasonality view: about a dozen results per route and market instead of a full year. |
| `includeUnpricedDays` | boolean | `false` | Keep dates with no published price, shown as empty. Blank dates are delivered as results and are billed like priced ones — see Pricing. Prices are never estimated or copied from another date. |
| `includeSeasonality` | boolean | `true` | Adds the monthly cheapest and typical price, the cheapest-to-priciest ranking and the comparison columns. |

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `route` | string | The route this result belongs to, as `ORIGIN-DESTINATION`. |
| `origin`, `destination` | string | The places used, as the source identifies them. |
| `originName`, `destinationName` | string | Readable names. |
| `originType`, `destinationType` | string | `city` or `airport`. |
| `originQuery`, `destinationQuery` | string | Exactly what you typed, so you can trace every result back to your input. |
| `date` | string | Departure date, `YYYY-MM-DD`. |
| `weekdayName`, `isWeekend` | string, boolean | Day of the week, and whether it is a weekend. |
| `month` | string | `YYYY-MM`, ready to group by. |
| `daysFromCollection` | integer | How far ahead of collection this date is. |
| `price` | number | Lowest published price for departing on this date. Empty when the source published none. |
| `hasPrice` | boolean | Whether a price was published for this date. |
| `currency` | string | The currency the price actually came back in. |
| `currencyRequested`, `currencyMatchesRequest` | string, boolean | What you asked for, and whether it matched. |
| `priceBand` | string | The source's own `low` / `medium` / `high` grouping for this date. |
| `market`, `marketName`, `language` | string | Which storefront the price came from. |
| `isCheapestDate` | boolean | The cheapest date in the range you collected, for this route and market. |
| `isCheapestInMonth` | boolean | The cheapest date within its own month. Ties go to the earlier date. |
| `priceRank` | integer | 1 is the cheapest date in the range. |
| `cheapestPriceInMonth`, `medianPriceInMonth`, `pricedDatesInMonth` | number, number, integer | Month-level context for this date. |
| `percentAboveCheapest` | number | How much more than the cheapest date this date costs, as a percentage. |
| `percentVsMedian` | number | How this date compares with the typical price for the range. |
| `cheapestPriceInRange`, `cheapestDateInRange`, `medianPriceInRange`, `pricedDatesInRange` | number, string, number, integer | The same context at range level, repeated on every result so a single row stands alone. |
| `isEstimated` | boolean | Always true: the source publishes these as indicative lowest prices, not as a quote. |
| `source` | string | `skyscanner`. |
| `collectedAt` | string | When the run collected this route. |

A run summary is also saved with the run: how many calendars were collected, how many routes had nothing published, and how many could not be read.

### Pricing

You pay per result, plus a small amount for each extra market you compare.

| What you pay for | Price |
| --- | --- |
| Each departure date delivered — with its price, or blank if you keep dates with no published price | $0.0012 (that is $1.20 per 1,000) |
| Each extra market compared, beyond the first, for a route | $0.020 |

**Worked example.** One route, one market, the whole published year is about 360 dates: **$0.43**. Add two comparison markets and you get about 1,080 dated prices across three storefronts: 1,080 × $0.0012 = $1.30, plus 2 × $0.020 for the extra markets = **$1.34 for the run**.

**One setting can multiply that.** *Include dates with no published price* delivers every date in the range, blanks included, and a blank date is a result like any other and costs the same $0.0012. On a thin route that published 42 dates spread over 205 days, leaving it off gives you 42 results for **$0.05**; switching it on gives you 205 results for **$0.25** — the same 42 prices, five times the bill. It is worth it when you need an unbroken day-by-day series; leave it off if you only want the dates that are actually sold.

The extra-market charge applies to any comparison storefront that answered — including one that answers that it sells nothing on your route, since that answer is the comparison you asked for. A storefront that could not be read is never charged.

Want it cheaper? Turn on *only the cheapest date in each month* and one route in one market costs about **$0.02** — roughly a dozen results instead of 360 — or narrow the date range to the months you care about.

### Limits & what this actor cannot do

- **It gives you one lowest price per departure date, not a list of flights.** There are no airlines, flight numbers, departure times, stop counts or fare rules in these results — the source does not publish them on this view.
- **The number is the lowest price published for departing on that date.** The source does not label it by trip length or fare type on this view, so treat it as an indicative floor for that day rather than a specific ticket.
- **Prices are a snapshot at the moment of collection.** Airline pricing changes continuously, and a price seen now may be gone hours later.
- **This actor does not book, hold or reserve anything**, and it cannot guarantee that a price is still available. The source itself describes these as estimated lowest prices.
- **Markets are separate sets of fares, not one price converted.** Never convert between markets to compare them: the same route can have a genuinely different cheapest date in two markets on the same day. Every result carries its own market and currency for exactly this reason.
- **About a year ahead, rolling.** The source publishes roughly 360 departure dates starting from the day you run it. Dates beyond that do not exist yet.
- **Thin routes are sparse.** On a quiet route the source publishes prices for some dates and nothing for others. Missing dates are left out, or shown empty if you ask for them — never filled in with a neighbouring date's price.
- **A route with nothing published is reported as having nothing**, and a route the source would not serve is reported as a failed read. The two are counted separately in the run summary, and a run where nothing could be read ends as a failed run rather than an empty success.
- **An answer that cannot be used is a failed read too.** You are never told a route publishes no dates on the strength of an answer this actor could not read. Where only some dates come back in a form it cannot use, those dates are left out, counted in the run summary and never guessed at.
- **Routes are seasonal.** A real route with no departures in the window you asked for is a legitimate empty result, not an error.
- **How long a run takes depends on its size** and on how quickly the source answers. No fixed speed is promised.
- **The source's terms govern automated access.** You are responsible for using the data lawfully and in line with those terms.

### FAQ

**Do I need a Skyscanner account?**
No. No account, no sign-up, nothing to connect.

**Does it need my login or password?**
No. You supply a route and a market and nothing else.

**Can I schedule it?**
Yes. Schedule it daily or weekly and compare the results over time — that is how a price-drop or best-time-to-book product gets built. Very frequent runs are not worth it: these prices move over days, not minutes.

**Are these prices bookable?**
Treat them as indicative. They are the lowest prices the source publishes for each date, and airline pricing moves constantly. This actor reports what is published; it does not hold or book a seat.

**Why do two markets disagree about the cheapest date?**
Because they are genuinely different sets of fares. A storefront in one country can be sold inventory another one is not. That disagreement is a real signal, and it is the reason the comparison feature exists.

**Can I get airlines, flight times or stops?**
Not from this actor — the source does not publish them alongside these dated prices. Use it to find the right dates, then search those dates for the flight detail.

**What happens if a route does not exist?**
You get a clear failed read naming the route, never a silent empty result that looks like "no flights".

# Actor input Schema

## `origin` (type: `string`):

Where the trip starts. Use an airport code (LHR), a city code (LON, NYC) or just the place name (London). Codes are used exactly as you type them; names are matched for you.

## `destination` (type: `string`):

Where the trip ends. Use an airport code (JFK), a city code (NYC) or the place name (New York).

## `additionalRoutes` (type: `array`):

Extra routes to collect in the same run, one per line, written as "LHR-JFK" or "London > New York". Each route is collected in every market you selected. Up to 20.

## `placeMatching` (type: `string`):

How a place NAME is matched. "Whole city" covers every airport serving that city, which is what most travellers compare. "Single airport" picks one airport instead. Codes you type yourself are always used as given.

## `market` (type: `string`):

The country storefront to price the route in. This chooses which fares you see, not just the currency — the same route can have a different cheapest date in different markets.

## `currency` (type: `string`):

The currency prices are shown in. Leave it on "Match the market" and each market is priced the way travellers there see it; pick a single currency only when you need every market in one unit. Whatever you pick, every result is labelled with the currency that actually came back.

## `locale` (type: `string`):

The language the market is browsed in. It does not change the prices; it is recorded on every result so you can tell runs apart.

## `compareMarkets` (type: `array`):

Collect the same route from other country storefronts as well, one per line. Write just the country ("UK") to use its own currency, or "UK:USD" to price it in a currency you choose. Markets are separate sets of fares, so the cheapest date can genuinely differ between them. Up to 7.

## `startDate` (type: `string`):

Only keep dates on or after this day, written as YYYY-MM-DD. Leave empty to start from the first date the source publishes.

## `endDate` (type: `string`):

Only keep dates on or before this day, written as YYYY-MM-DD. Leave empty to run to the end of the published year.

## `maxDaysPerMarket` (type: `integer`):

Caps how many departure dates are kept for each route in each market, counting from the earliest. The source publishes about 360 dates; lower it to keep runs small.

## `onlyCheapestPerMonth` (type: `boolean`):

Turn this on for a compact seasonality view: about a dozen results per route and market instead of a full year of days.

## `includeUnpricedDays` (type: `boolean`):

Keep dates the source published no price for, with an empty price. Useful when you want to see gaps in a thin route rather than a shorter list. These blank dates are delivered as results and are billed the same as priced ones, so a thin route can cost several times more with this on. Prices are never estimated or carried over from another date.

## `includeSeasonality` (type: `boolean`):

Adds the cheapest and typical price for each month, how far above the cheapest date each date sits, and a cheapest-to-priciest ranking.

## Actor input object example

```json
{
  "origin": "LHR",
  "destination": "JFK",
  "additionalRoutes": [
    "LGW-JFK",
    "Manchester > New York"
  ],
  "placeMatching": "city",
  "market": "US",
  "currency": "auto",
  "locale": "auto",
  "compareMarkets": [
    "UK",
    "IN:INR",
    "JP:JPY"
  ],
  "startDate": "2026-11-01",
  "endDate": "2027-02-28",
  "maxDaysPerMarket": 400,
  "onlyCheapestPerMonth": false,
  "includeUnpricedDays": false,
  "includeSeasonality": true
}
```

# Actor output Schema

## `priceCalendar` (type: `string`):

One result per departure date, route and market, with the lowest published price for that date.

## `priceCalendarCsv` (type: `string`):

The same results as a spreadsheet-ready file.

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

How many calendars were collected, how many routes had nothing published, and how many could not be read.

# 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 = {
    "origin": "London",
    "destination": "New York"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/skyscanner-flight-price-calendar-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 = {
    "origin": "London",
    "destination": "New York",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/skyscanner-flight-price-calendar-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 '{
  "origin": "London",
  "destination": "New York"
}' |
apify call apt_marble/skyscanner-flight-price-calendar-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/skyscanner-flight-price-calendar-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/k1aRsGdjYNhdgu3Ho/builds/c1jJ63ErkO4FXUCPS/openapi.json
