# Skyscanner Flights Scraper - One-Way, Return and Multi-City (`dami_studio/skyscanner-flights-scraper`) Actor

Search Skyscanner for one-way, return and multi-city flights on the routes and dates you give. Keep only what fits: stops, airlines, times, journey length, price. One row per itinerary with the lowest price, who sells it, flight numbers, times and stops.

- **URL**: https://apify.com/dami\_studio/skyscanner-flights-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.05 / 1,000 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?

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 Flights Scraper

It searches Skyscanner for the routes and dates you give, one-way, return or multi-city, and hands back one row per itinerary: the lowest price, who sells it, and the flights with their times and stops. Then it keeps only the itineraries you'd actually book, by stops, airline, time of day, journey length or price.

A run takes three to five minutes. A new Skyscanner search shows a few results straight away and fills in the full list about three minutes later, and this Actor waits for the full list, since a filter run on a ten-row preview would tell you very little. The searches in a run wait together: one takes about three minutes, twenty take about four and a half.

### Input

```json
{
  "routes": [
    { "origin": "JFK", "destination": "LAX", "date": "2026-10-20" },
    { "origin": "LHR", "destination": "BCN", "date": "2026-11-12", "returnDate": "2026-11-19" },
    { "legs": [
      { "origin": "LHR", "destination": "BCN", "date": "2026-11-12" },
      { "origin": "BCN", "destination": "FCO", "date": "2026-11-15" },
      { "origin": "FCO", "destination": "LHR", "date": "2026-11-19" }
    ] }
  ],
  "adults": 2,
  "childAges": [8],
  "maxStops": "0",
  "sortBy": "fastest",
  "maxItinerariesPerRoute": 20
}
```

#### Routes

- **One-way**: `{"origin": "JFK", "destination": "LAX", "date": "2026-10-20"}`, or `JFK-LAX-2026-10-20`. Leave the date out and it searches the day 30 days after the run.
- **Return**: add `"returnDate"`, or write `JFK-LAX-2026-10-20-2026-10-27`.
- **Multi-city**: two or three legs under `"legs"`, in order, each with its own date. A leg is one journey from its origin to its destination, however many flights it takes.
- **Several departure days**: add `"lastDate"` to a one-way or return route and it searches every day from `"date"` to `"lastDate"`, up to 7 days. Each day is a search of its own, so 20 to 23 October is four searches and four lists of itineraries, and you pay for each search and for what it brings back. On a return the return date moves along with the departure, so every trip is the same length.
- **Codes**: three-letter airport codes (LHR, JFK), or a city code for every airport in a city (LON, NYC, PAR, TYO and twenty others).

A run makes up to 50 searches, a range counting once for each day.

#### Skyscanner links

Put links to Skyscanner results pages in `searchUrls` and each one becomes a search. The airports, dates, adults, children's ages, cabin and "direct flights only" are read from the link; the market, the currency and the filters come from the rest of the input. The link from your browser's address bar works, and so do the referral links Skyscanner documents for partners. A link for a whole month, or for more than three legs, is refused. Routes and links can go in the same run.

#### Passengers and cabin

`adults` is 1 to 8. `childAges` takes one age per child, 0 to 17, so `[8, 1]` is an eight-year-old and a baby. Under 2 counts as an infant, and each infant needs an adult. The price on every row is for the whole group, children included.

`cabinClass` is economy, premium\_economy, business or first. `market`, `locale` and `currency` choose which Skyscanner to search and what to price in: US, en-US and USD by default, or UK, en-GB and GBP for the UK site.

#### Filters and order

| Setting | What it does |
|---|---|
| `maxStops` | `0` for non-stop only, `1` or `2` for at most that many. Counted per leg, so on a return both directions have to pass |
| `maxPrice` | Drops anything dearer, comparing the whole group's price in the search currency |
| `maxDurationHours` | The longest a leg may take, first take-off to last landing, in hours. `7.5` works |
| `airlines` | Keeps an itinerary only if every flight in it is sold by one of these |
| `excludeAirlines` | Drops an itinerary if any flight is sold by one of these, or flown by one on another airline's behalf |
| `departureTimeRange`, `arrivalTimeRange` | The outbound leg, local time: `06:00-12:00`. `22:00-06:00` runs past midnight |
| `returnDepartureTimeRange`, `returnArrivalTimeRange` | The same for the journey home, on return trips |
| `sortBy` | `cheapest`, `fastest`, `earliest` or `best` |
| `maxItinerariesPerRoute` | How many to keep from each search, counted after the filters and the order. 500 unless you change it, up to 1,000 |

Airlines go in as two-character codes (BA, U2, B6) or as names. Names match whole words, so "Delta" also finds flights run as Delta Connection, and "Air" finds Air France but not Vueling Airlines.

`fastest` counts the whole trip, both ways on a return. `best` is this Actor's own ranking, not the one on Skyscanner's site: an itinerary's price divided by the cheapest one's, plus its journey time divided by the quickest one's, lowest first. With the cheapest at $100 and the quickest at 2 hours, $120 in 2 hours scores 2.2 and $100 in 5 hours scores 3.5, so the dearer, quicker one comes first.

Filters don't make a run any shorter. Every search still waits out its three minutes; the filters only decide what comes back.

Bad input is refused before anything is searched: a code it doesn't recognise, a date in the past or more than a year away, a return before its departure, more infants than adults. The `PROBLEMS` record in the run's key-value store says what was wrong. A filter it can't read, such as `"departureTimeRange": "mornings"`, stops the whole run, because every search would have used it.

### Output

One row per itinerary, in the order you asked for within each search. This one is real, a San Francisco to Seattle one-way, with the flight detail shortened:

```json
{
  "ok": true,
  "recordType": "itinerary",
  "route": "SFO-SEA",
  "tripType": "one-way",
  "date": "2026-11-10",
  "returnDate": null,
  "price": 138.2,
  "currency": "USD",
  "pricingOptions": 8,
  "cheapestSeller": "Expedia",
  "carriers": ["Alaska Airlines"],
  "flightNumbers": ["AS2424", "AS773"],
  "departureAirport": "SFO",
  "arrivalAirport": "SEA",
  "departure": "2026-11-10T13:29:00",
  "arrival": "2026-11-10T19:25:00",
  "durationMinutes": 356,
  "stops": 1,
  "legs": [
    {
      "from": "SFO", "to": "SEA", "stops": 1, "durationMinutes": 356,
      "flights": [
        { "flightNumber": "AS2424", "carrierCode": "AS", "from": "SFO", "to": "SNA", "departure": "2026-11-10T13:29:00", "arrival": "2026-11-10T15:05:00", "operatedBy": "Horizon Air AS Alaska Airlines" },
        { "flightNumber": "AS773", "carrierCode": "AS", "from": "SNA", "to": "SEA", "departure": "2026-11-10T16:30:00", "arrival": "2026-11-10T19:25:00", "operatedBy": null }
      ]
    }
  ],
  "itineraryId": "16216-2611101329--32593-1-16177-2611101925",
  "market": "US",
  "locale": "en-US",
  "adults": 1,
  "childAges": [],
  "cabinClass": "economy",
  "searchedAt": "2026-09-13T00:48:31.000Z"
}
```

| Field | What it is |
|---|---|
| `route`, `tripType` | The search: `JFK-LAX` for one-way and return, every leg for multi-city (`LHR-BCN, BCN-FCO, FCO-LHR`); `one-way`, `return` or `multi-city` |
| `date`, `returnDate` | The departure date, and the return date on a return trip |
| `price` | The lowest of the offers, in `currency`, for everyone on the booking |
| `pricingOptions` | How many offers Skyscanner listed for this itinerary |
| `cheapestSeller` | Who sells it at that price, an airline or a travel agent |
| `carriers`, `flightNumbers` | The airlines selling the flights, and the flight numbers in order |
| `departureAirport`, `departure`, `arrivalAirport`, `arrival` | Where and when the whole trip starts and ends, in local time with no time zone. On a return both airports are home; each leg's own are under `legs` |
| `durationMinutes`, `stops` | For the whole trip. On a return both directions are added together |
| `legs[].flights[]` | Each flight: number, airline and its code, who flies it when that is another airline, airports, times and minutes |
| `itineraryId` | Skyscanner's id for this set of flights. It is built from the airports, the times, the airline and the stops, so the same flights on the same day keep the same id |

The dataset holds itineraries and nothing else. A route that was refused, came back empty, had nothing that passed the filters or couldn't be searched is listed in the `PROBLEMS` record in the run's key-value store, with an `errorCode` and the reason in plain words, and the run's status message says how many there were. The notes themselves are never charged; what a route costs is set out below. `RUN_SUMMARY`, next to it, has the counts for the whole run, including how many itineraries were found and how many passed the filters.

### What you pay for

$0.005 for each search that finishes, plus $0.052 per 1,000 itineraries delivered, both flat on every plan. A search is one route on one departure day, so a return trip is one search and a three-day range is three.

The search fee is charged once Skyscanner's full list is in, whether your filters keep a hundred itineraries or none, because the search itself is the work. Itineraries the filters drop aren't charged. A search that is refused or can't be made costs nothing, and one that never finishes is charged only for the itineraries it delivers. If you set a maximum charge for the run, the Actor works out before it starts how many searches and itineraries that covers, and routes that would go past it are left out and listed in `PROBLEMS`.

### What it doesn't do

- **No booking links.** Skyscanner's search results don't carry them. Each row names the seller, and you book with the seller or on Skyscanner.
- **No nearby airports.** For every airport in a city, use its city code, such as LON or NYC. A link that asks for nearby airports is searched on the airports it names, and `PROBLEMS` says so.
- **No flexible dates beyond a week.** A range covers up to 7 departure days, and there is no whole-month search.
- **Three legs at most** on a multi-city trip, and the time filters only look at the first of them.
- **No fare rules or baggage.** You get the price and the flights, not what the fare includes.

The prices are what Skyscanner showed at the moment of the search. Fares move, and a seller's final price can be different.

### Good to know

- Twenty searches in one run take about four and a half minutes, not twenty lots of three, so put your routes in one run.
- Return trips bring back far more itineraries than one-way. A London to Barcelona return found 1,160 in a test where the one-way found 165, and `maxItinerariesPerRoute` keeps the first 500 unless you change it.
- With children on a search, the Actor only uses Skyscanner's prices once Skyscanner has confirmed the children and infants it priced. If it can't confirm them, the route goes in `PROBLEMS` and nothing is charged for it.
- Now and then a route can't be searched on a run. It is listed in `PROBLEMS`, it isn't charged, and it can go in your next run.
- Once in a while a search never finishes. That route then gives you what Skyscanner had at the end of the wait, usually ten itineraries before the filters, plus an entry in `PROBLEMS` saying the list may be incomplete.
- The airport and city codes come from a public list taken in September 2026. A brand-new airport code may not be on it yet; if one is refused, tell us on the Issues tab. A city in a Skyscanner link that the Actor can't read is refused too, with a note to write the route with codes instead.

# Actor input Schema

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

One search per route and date. A one-way route is {"origin": "JFK", "destination": "LAX", "date": "2026-10-20"} or the short form "JFK-LAX-2026-10-20". For a return, add "returnDate", or write "JFK-LAX-2026-10-20-2026-10-27". For a multi-city trip, put two or three legs in order under "legs", each with its own origin, destination and date. Add "lastDate" to a one-way or return route to search every departure day from "date" to it, 7 days at most: each day is a search of its own, so four days means four lists of itineraries to pay for, and on a return the return date moves along with it. Use airport codes such as LHR or JFK, or a city code such as LON, NYC or PAR for every airport in that city. With no date, a one-way route searches the day 30 days after the run. Up to 50 searches a run. They wait together, so five take about as long as one.

## `searchUrls` (type: `array`):

Links to Skyscanner results pages, one per line, each run as a search. The airports, dates, adults, children's ages, cabin and 'direct flights only' come from the link; the market, currency and filters come from this form. A link for a whole month, or for more than three legs, is refused. If a link asks for nearby airports, only the airports it names are searched, and PROBLEMS says so.

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

Which country's Skyscanner to search, as a two-letter code: US, UK, CA, DE and so on. For the United Kingdom it is UK; GB is accepted and treated as UK.

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

Locale sent with the search, such as en-US or en-GB. Keep it in step with the market.

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

Currency for the prices, as a three-letter code such as USD, GBP or EUR.

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

Adult passengers, 1 to 8. The price on each row is for everyone on the booking, children included, not a price per person.

## `childAges` (type: `array`):

One age per child: \[8, 1] is an eight-year-old and a baby. Ages 0 to 17, up to 8 children. Under 2 counts as an infant, and each infant needs an adult. Leave it empty if only adults are flying.

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

Which cabin to price.

## `maxStops` (type: `string`):

Counted per leg, so on a return both directions have to pass.

## `maxPrice` (type: `number`):

For the whole group, in the currency above. Anything dearer is dropped.

## `maxDurationHours` (type: `number`):

Per leg, from its first take-off to its last landing, connections included. 7.5 is seven and a half hours.

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

Two-character codes such as BA, U2 or B6, or names such as British Airways. An itinerary stays only if every flight in it is sold by one of them.

## `excludeAirlines` (type: `array`):

Codes or names, as above. An itinerary is dropped if any flight in it is sold by one of them, or flown by one on another airline's behalf.

## `departureTimeRange` (type: `string`):

When the outbound leg leaves, local time, on the 24-hour clock: 06:00-12:00. A range such as 22:00-06:00 runs past midnight. On a multi-city trip this and the arrival time apply to the first leg only.

## `arrivalTimeRange` (type: `string`):

When the outbound leg lands, local time at the airport it lands at, such as 12:00-20:00.

## `returnDepartureTimeRange` (type: `string`):

Return trips only: when the journey home leaves, local time, such as 15:00-21:00.

## `returnArrivalTimeRange` (type: `string`):

Return trips only: when the journey home lands, local time.

## `sortBy` (type: `string`):

Fastest counts the whole trip, both ways on a return. Best is this Actor's own ranking, not the one on Skyscanner's site: each itinerary's price divided by the cheapest one's, plus its journey time divided by the quickest one's, lowest first.

## `maxItinerariesPerRoute` (type: `integer`):

Taken from the top once the filters and the order have run, so Fastest first with 10 gives the ten quickest that pass. A busy route has several hundred itineraries and a return can have over a thousand, and you pay for each one delivered.

## Actor input object example

```json
{
  "routes": [
    {
      "origin": "JFK",
      "destination": "LAX"
    }
  ],
  "market": "US",
  "locale": "en-US",
  "currency": "USD",
  "adults": 1,
  "cabinClass": "economy",
  "maxStops": "any",
  "sortBy": "cheapest",
  "maxItinerariesPerRoute": 500
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset, one per itinerary: route and trip type, dates, price and currency, cheapest seller, number of offers, airlines, flight numbers, departure and arrival (local times), minutes, stops and the itinerary id, with each leg and flight under legs.

## `problems` (type: `string`):

The PROBLEMS record: one entry per route that was refused, came back empty, had nothing that passed the filters, could not be searched or may be incomplete, with the route, date, error code and the reason. An empty list means nothing went wrong. The entries themselves are never charged; a finished search that the filters emptied still counts as a search.

# 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": [
        {
            "origin": "JFK",
            "destination": "LAX"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/skyscanner-flights-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": [{
            "origin": "JFK",
            "destination": "LAX",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/skyscanner-flights-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": [
    {
      "origin": "JFK",
      "destination": "LAX"
    }
  ]
}' |
apify call dami_studio/skyscanner-flights-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/skyscanner-flights-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/YUkgDwPJTMYNBWClw/builds/xy2FHypF1zxhbY3Pw/openapi.json
