# Skyscanner Flight Scraper — Fares, Legs & Agents (`diopside/skyscanner-flights`) Actor

Drop-in compatible with memo23/skyscanner-scraper — cheaper and reliable. Flight itineraries from Skyscanner's own search API: price, all booking agents with their quotes, legs, segments, carriers, stops and derived layover durations. One-way, return and multi-city. No browser, no captcha solver.

- **URL**: https://apify.com/diopside/skyscanner-flights.md
- **Developed by:** [DIOPSIDE AI](https://apify.com/diopside) (community)
- **Categories:** Travel, Other, Agents
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 flight itineraries

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Skyscanner Flight Scraper — Fares, Legs & Agents

**Drop-in compatible with `memo23/skyscanner-scraper` — cheaper on real result volumes, and it
returns every agent's quote instead of just the cheapest one.** Give it a route and dates (or paste
a Skyscanner results URL) and it returns flight itineraries from Skyscanner's own search API: total
price, every booking agent quoting that itinerary with its Skyscanner rating and deep link, all
legs and segments, carriers, stop counts, and layover durations that Skyscanner itself never
publishes.

No browser, no captcha solver, no login.

### Why this one

**It returns all the quotes, not just the cheapest.** A single London–New York itinerary is
typically sold by 10–12 different agents at prices spread over $80. Most Skyscanner actors flatten
that to one number. This one gives you `pricingOptions` — every agent, its price, its Skyscanner
rating, whether it is the airline itself, and its own booking link — so you can compare sellers,
not just routes.

**Layover durations are computed for you.** Skyscanner sends segment times but never the gap
between them. Every leg here carries a `layovers` array with the connecting airport and the wait in
minutes, which is the field most people end up writing themselves.

**Sorting happens before the limit.** `resultSort: "cheapest"` with `resultLimit: 20` gives you the
20 cheapest itineraries out of the 1,500 the route actually returned — not the first 20 in
Skyscanner's default order. Several actors in this category apply the limit first, which quietly
makes "cheapest" meaningless.

**It fails loudly.** Skyscanner's search API sits behind PerimeterX, which answers with a small
JSON redirect stub — sometimes alongside an HTTP 200. Treating that as data is how a scraper
"succeeds" with an empty dataset. This actor inspects the body, rotates to a new exit IP, and if it
genuinely cannot get through it **fails the run with the reason in the status message** rather than
reporting a successful run with nothing in it.

### Input

Field names match `memo23/skyscanner-scraper`, so an existing input works unchanged.

| Field | Type | Default | Notes |
|---|---|---|---|
| `startUrls` | array | — | `/transport/flights/{from}/{to}/{YYMMDD}/{YYMMDD?}/` URLs. Route and dates come from the URL. |
| `departureAirport` | string | — | IATA code (`LHR`) or city name (`London`). A code searches that airport only; a city searches all its airports. |
| `arrivalAirport` | string | — | Same. |
| `departureDate` | string | — | `YYYY-MM-DD` or `YYMMDD`. |
| `returnDate` | string | — | Omit for one-way. |
| `cabinClass` | string | `economy` | `economy`, `premium_economy`, `business`, `first`. |
| `adults` / `children` / `infants` | integer | `1` / `0` / `0` | Children are priced at age 10, infants at age 1. |
| `multiCityLeg2Destination` / `...Date` | string | — | Adds a third leg. `Leg3` adds a fourth. |
| `skyscannerMarket` | string | `US` | Fares and available agents differ by market. |
| `skyscannerCurrency` | string | `USD` | |
| `skyscannerLocale` | string | `en-US` | |
| `resultSort` | string | `default` | `default`, `score`, `cheapest`, `fastest`, `departure`. |
| `resultLimit` | integer | no limit | Itineraries kept **and billed** per search. See the cost note below. |
| `filterNonStop` / `filterOneStop` / `filterTwoPlusStops` | boolean | `false` | Additive. All three off means no stop filter. |
| `includeNearbyAirports` | boolean | `false` | |
| `maxItems` | integer | — | Alias of `resultLimit`. |
| `proxyConfiguration` | object | Apify **RESIDENTIAL** | Required — see below. |

#### A residential proxy is required

Skyscanner's search API rejects datacenter exit IPs. Measured on the Apify platform:

| Exit tier | Search API |
|---|---|
| Raw Apify IP (no proxy) | Blocked by PerimeterX, every attempt |
| Apify datacenter proxy | Blocked by PerimeterX, every attempt |
| Apify **RESIDENTIAL** proxy | Passes on ~6 exits in 7 |

The default `proxyConfiguration` is already RESIDENTIAL. A run configured without it rotates
through a dozen exits and then fails with an explicit message. (A headless *and* a headful stealth
browser both hit a hard captcha on datacenter IPs, so this is a property of the exit IP, not of the
client — a browser would not help.)

### Output

One record per itinerary.

```json
{
  "id": "13554-2612080600--31799-1-12712-2612081315|12712-2612151935--31799-1-13554-2612160655",
  "searchOrigin": "LHR",
  "searchDestination": "JFK",
  "departureDate": "2026-12-08",
  "returnDate": "2026-12-15",
  "cabinClass": "ECONOMY",
  "adults": 1,
  "currency": "USD",
  "market": "US",
  "price": 482,
  "priceFormatted": "$482",
  "pricePerAdult": 482,
  "agentName": "Flightnetwork",
  "bookingUrl": "https://www.skyscanner.com/transport_deeplink/4.0/US/en-US/USD/fnus/...",
  "isDirect": false,
  "stopCount": 1,
  "totalDurationInMinutes": 1520,
  "departure": "2026-12-08T06:00:00",
  "arrival": "2026-12-08T13:15:00",
  "carrierNames": ["SWISS"],
  "legs": [
    {
      "origin": { "id": "LHR", "name": "London Heathrow", "city": "London", "country": "United Kingdom" },
      "destination": { "id": "JFK", "name": "New York John F. Kennedy", "city": "New York", "country": "United States" },
      "departure": "2026-12-08T06:00:00",
      "arrival": "2026-12-08T13:15:00",
      "durationInMinutes": 795,
      "stopCount": 1,
      "carriers": [{ "name": "SWISS", "logoUrl": "https://logos.skyscnr.com/images/airlines/favicon/LX.png" }],
      "segments": [{ "flightNumber": "317", "marketingCarrier": { "name": "SWISS" }, "...": "..." }],
      "layovers": [
        { "airport": "ZRH", "arrival": "2026-12-08T08:40:00", "departure": "2026-12-08T09:55:00", "durationInMinutes": 75 }
      ]
    }
  ],
  "pricingOptions": [
    { "agentId": "fnus", "agentName": "Flightnetwork", "agentRating": 4.51, "agentIsCarrier": false,
      "price": 482, "bookingProposition": "PBOOK", "bookingUrl": "https://www.skyscanner.com/transport_deeplink/..." }
  ],
  "pricingOptionCount": 10,
  "isSelfTransfer": false,
  "isChangeAllowed": false,
  "isCancellationAllowed": false,
  "tags": ["cheapest"],
  "score": 0.584649,
  "scrapedAt": "2026-09-20T00:42:02+00:00"
}
```

Missing upstream values are `null`, never a crash: Skyscanner omits `farePolicy`, `score` and whole
pricing options on some itineraries.

### Sizes and cost

A busy long-haul route returns a lot: **LHR→JFK return produced 1,546 itineraries** in one search,
settling in about 30 seconds. Short-haul and thin routes return tens.

Pricing is per search plus per itinerary:

| Event | Price |
|---|---|
| Actor start | $0.00005 |
| Flight search | $0.025 |
| Flight itinerary | $0.0006 |

A search costs the same in bandwidth whether you keep 25 itineraries or all 1,546, so that fixed
cost is billed as a fixed event instead of being buried in a per-result price that punishes large
pulls. Against `memo23/skyscanner-scraper` ($0.007 start + $0.001 per result) that works out
cheaper from roughly 50 results per search upward, and much cheaper on full pulls — 1,546
itineraries cost **$0.95 here versus $1.55** there.

**Use `resultLimit`.** Without it, one search over a busy route bills 1,500+ itineraries. If you
only want the cheapest few, set `resultSort: "cheapest"` and `resultLimit: 20` — you still get the
cheapest 20 of the whole result set.

### Use cases

- **Fare monitoring** — run a route on a schedule and track `price` and `scrapedAt` over time.
- **Agent/OTA price comparison** — `pricingOptions` shows what each seller charges for the
  identical itinerary, and how Skyscanner rates them.
- **Route and schedule research** — carriers, flight numbers, durations and layovers per segment.
- **Travel-policy checks** — filter on `isDirect`, `totalDurationInMinutes`, layover length or
  `isChangeAllowed` / `isCancellationAllowed`.

### Switching from `memo23/skyscanner-scraper`

Change the actor id. The input field names, date formats (`YYYY-MM-DD` and `YYMMDD`), cabin class
values, stop filters and the `startUrls` / filter-fields precedence are all the same.

Two differences worth knowing:

- Output field names are ours, not the incumbent's flattened 180-column shape. The dataset view or
  `?format=csv` gives you a flat table; `legs` and `pricingOptions` stay nested because that is
  where the extra data lives.
- `proxyConfiguration` defaults to RESIDENTIAL and is genuinely required (see above).

### Notes

- Skyscanner entity ids are resolved at run time through Skyscanner's own autosuggest service, so
  new or renamed airports work without an actor update, and nothing is hardcoded.
- Fares move constantly. `scrapedAt` on every record is what makes an old dataset interpretable.
- Public flight pricing only. No personal data.

# Actor input Schema

## `startUrls` (type: `array`):

Paste one or more <code>/transport/flights/{origin}/{destination}/{YYMMDD}/{YYMMDD?}/</code> URLs from skyscanner.com or skyscanner.net. Route and dates come from the URL; passenger counts, cabin class and region always come from the fields below. Anything that is not a flights URL (e.g. the homepage) falls back to the search filters.

## `departureAirport` (type: `string`):

City or IATA code, e.g. <code>LHR</code> or <code>London</code>. An airport code searches that airport only; a city name searches all of the city's airports. Ignored when a flights URL is given above.

## `arrivalAirport` (type: `string`):

City or IATA code. Ignored when a flights URL is given above.

## `departureDate` (type: `string`):

<code>YYYY-MM-DD</code> or <code>YYMMDD</code>.

## `returnDate` (type: `string`):

Leave empty for a one-way search.

## `cabinClass` (type: `string`):

Cabin to price.

## `adults` (type: `integer`):

Passengers aged 16+.

## `children` (type: `integer`):

Passengers aged 2-15.

## `infants` (type: `integer`):

Passengers under 2.

## `multiCityLeg2Destination` (type: `string`):

Adds a third leg continuing from the arrival airport. Leave empty for a normal one-way or return search.

## `multiCityLeg2Date` (type: `string`):

Date for leg 2, <code>YYYY-MM-DD</code> or <code>YYMMDD</code>. Required if leg 2 destination is set.

## `multiCityLeg3Destination` (type: `string`):

Adds a fourth leg continuing from the leg 2 destination.

## `multiCityLeg3Date` (type: `string`):

Date for leg 3, <code>YYYY-MM-DD</code> or <code>YYMMDD</code>. Required if leg 3 destination is set.

## `skyscannerMarket` (type: `string`):

Two-letter market, e.g. <code>US</code>, <code>GB</code>, <code>DE</code>. Fares and available agents differ by market.

## `skyscannerCurrency` (type: `string`):

Three-letter currency code, e.g. <code>USD</code>, <code>EUR</code>, <code>GBP</code>.

## `skyscannerLocale` (type: `string`):

e.g. <code>en-US</code>, <code>en-GB</code>, <code>de-DE</code>.

## `resultSort` (type: `string`):

<code>default</code> keeps Skyscanner's own "Best" ranking. The others sort before the result limit is applied, so <code>cheapest</code> + a limit of 20 gives you the 20 cheapest itineraries rather than the first 20.

## `resultLimit` (type: `integer`):

Caps how many itineraries each search stores and bills. A busy long-haul route returns 500+ itineraries, so leaving this empty can be expensive. Empty or 0 means no limit.

## `filterNonStop` (type: `boolean`):

The three stop filters are additive. Leaving all three off means no stop filter.

## `filterOneStop` (type: `boolean`):

Include itineraries with exactly one stop.

## `filterTwoPlusStops` (type: `boolean`):

Include itineraries with two or more stops.

## `includeNearbyAirports` (type: `boolean`):

Also search airports near the origin and destination.

## `maxItems` (type: `integer`):

Alias of the per-search limit, accepted for compatibility with other Skyscanner actors.

## `proxyConfiguration` (type: `object`):

Skyscanner's search API is behind PerimeterX and rejects every datacenter exit IP we measured, so a residential proxy is required. The default below is the configuration the actor is tested with — change it only if you know your exits pass.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.skyscanner.net/transport/flights/lhr/jfk/261020/261027/"
    }
  ],
  "departureAirport": "LHR",
  "arrivalAirport": "JFK",
  "departureDate": "2026-10-20",
  "returnDate": "2026-10-27",
  "cabinClass": "economy",
  "adults": 1,
  "children": 0,
  "infants": 0,
  "skyscannerMarket": "US",
  "skyscannerCurrency": "USD",
  "skyscannerLocale": "en-US",
  "resultSort": "default",
  "resultLimit": 50,
  "filterNonStop": false,
  "filterOneStop": false,
  "filterTwoPlusStops": false,
  "includeNearbyAirports": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `itineraries` (type: `string`):

All itinerary records. Append ?format=csv for CSV.

## `datasetUrl` (type: `string`):

The default dataset.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.skyscanner.net/transport/flights/lhr/jfk/261020/261027/"
        }
    ],
    "departureAirport": "LHR",
    "arrivalAirport": "JFK",
    "departureDate": "2026-10-20",
    "returnDate": "2026-10-27",
    "resultLimit": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("diopside/skyscanner-flights").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 = {
    "startUrls": [{ "url": "https://www.skyscanner.net/transport/flights/lhr/jfk/261020/261027/" }],
    "departureAirport": "LHR",
    "arrivalAirport": "JFK",
    "departureDate": "2026-10-20",
    "returnDate": "2026-10-27",
    "resultLimit": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("diopside/skyscanner-flights").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 '{
  "startUrls": [
    {
      "url": "https://www.skyscanner.net/transport/flights/lhr/jfk/261020/261027/"
    }
  ],
  "departureAirport": "LHR",
  "arrivalAirport": "JFK",
  "departureDate": "2026-10-20",
  "returnDate": "2026-10-27",
  "resultLimit": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call diopside/skyscanner-flights --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,diopside/skyscanner-flights"
        }
    }
}
```

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/eYySbHQ9EStEO8qqD/builds/Ou0EaPuqeRfNNiq51/openapi.json
