# Kiwi Flight Search Scraper (`apt_marble/kiwi-flight-search-scraper`) Actor

Search Kiwi.com by route, date and cabin and get every itinerary as a row: fare, airlines, flight numbers, times, stops, layovers, included bags and a direct booking link. Flags the cheap trips built from separate tickets and scores each connection. 37 currencies, one-way or return.

- **URL**: https://apify.com/apt\_marble/kiwi-flight-search-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 $2.50 / 1,000 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

## Kiwi Flight Search Scraper

Search Kiwi.com the way a traveller would — one route, one date, one cabin — and get back every itinerary as a clean row: the fare, the airlines, the flight numbers, the exact departure and arrival times, the stops, every layover, and a link that opens that precise trip on Kiwi.com. It also tells you something almost no other flight dataset does: which of those cheap itineraries are stitched together from **separate tickets**, and how exposed the traveller is if one leg runs late. Run it for a single route or for a whole matrix of routes and dates, and get a table you can price against, model with, or ship straight into a product.

### What you can do with it

- **Track fares on the routes you care about.** Run it on a schedule and build your own price history for any route, in any of 37 currencies.
- **Compare an origin, a destination or a whole date range at once.** Several departure airports, several destinations and many dates in one run, each search kept separate and labelled. Every search carries its own complete set of results, so a flight that two of your searches both find is written once under each of them — group by `searchId` to compare searches, or deduplicate on `itineraryId` to count distinct flights.
- **Find the cheap fares other datasets miss.** Kiwi.com sells self-connected trips that no airline lists, so the cheapest row is often one a standard flight feed never shows you.
- **Judge a connection before you buy it.** Every result carries its separate-ticket flags, the shortest layover, airport changes, bag re-checks and protected connections, plus an overall risk level you can sort on.
- **Feed a comparison site or a travel product.** Every row ships with a working booking link for that exact itinerary.
- **Screen a route for airline mix and schedule.** Airlines actually flying it, the operating carrier behind each codeshare, real flight numbers and door-to-door duration.

### What you get

One row per itinerary. Real example, abridged (long identifiers and the booking link are shortened here):

```json
{
  "source": "kiwi",
  "market": "US",
  "currency": "USD",
  "requestedCurrency": "USD",
  "collectedAt": "2026-08-16T12:00:00.000Z",
  "searchId": "JFK-LAX-2026-09-15",
  "searchOrigin": "JFK",
  "searchDestination": "LAX",
  "requestedDepartureDate": "2026-09-15",
  "tripType": "oneway",
  "cabinClass": "ECONOMY",
  "price": 171,
  "priceFormatted": "$171",
  "priceEur": 147.8088,
  "durationMin": 359,
  "originCode": "JFK",
  "originName": "John F. Kennedy International",
  "destinationCode": "LAX",
  "destinationName": "Los Angeles International",
  "departureLocal": "2026-09-15T06:00:00",
  "arrivalLocal": "2026-09-15T08:59:00",
  "stopCount": 0,
  "carrierCodes": ["B6"],
  "carrierNames": ["JetBlue Airways"],
  "flightNumbers": ["B6123"],
  "pnrCount": 1,
  "isSelfTransfer": false,
  "isTrueHiddenCity": false,
  "includedHandBags": 1,
  "includedCheckedBags": 0,
  "connectionRiskLevel": "NONE",
  "riskFlags": [],
  "segments": [
    {
      "leg": "outbound",
      "flightNumber": "B6123",
      "carrierName": "JetBlue Airways",
      "fromCode": "JFK",
      "toCode": "LAX",
      "departureLocal": "2026-09-15T06:00:00",
      "arrivalLocal": "2026-09-15T08:59:00",
      "durationMin": 359,
      "cabinClass": "ECONOMY"
    }
  ],
  "layovers": [],
  "sellerName": "Kiwi.com",
  "bookingUrl": "https://www.kiwi.com/en/booking/?…",
  "resultsAvailable": 22
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `origins` | array | — | Where the trip starts: airport codes (`JFK`), city-wide codes (`NYC`), or names (`London Heathrow`, `Milan`). Several are allowed. A name that two airports answer equally well is refused with both codes named, rather than one of them being chosen for you — see below. |
| `destinations` | array | — | Where the trip ends, same formats. Several are allowed. |
| `tripType` | string | `oneway` | `oneway` or `return`. |
| `departureDates` | array | — | Outbound dates, `YYYY-MM-DD`. Each date is a separate search. |
| `returnDates` | array | — | Return dates for a return trip. Each is paired with each departure date. |
| `nightsMin` / `nightsMax` | integer | — | Instead of fixed return dates, let the search choose the return date inside this trip length. |
| `dateFlexibilityDays` | integer | `0` | Widen every date into a window of this many extra days (0–7). |
| `adults` | integer | `1` | Adult travellers (1–9). |
| `children` | integer | `0` | Children aged 2–11 in their own seat. |
| `infants` | integer | `0` | Lap infants; cannot exceed the number of adults. |
| `cabinClass` | string | `ECONOMY` | `ECONOMY`, `PREMIUM_ECONOMY`, `BUSINESS` or `FIRST_CLASS`. |
| `currency` | string | `USD` | One of 37 currencies. Every row also records the currency it was served in. |
| `market` | string | `US` | The market the search is prepared for; recorded on every row. |
| `locale` | string | `en` | Language for airline and airport names. |
| `maxResultsPerSearch` | integer | `50` | Itineraries to keep per route and date, up to 1,000. |
| `sortBy` | string | `price` | `price`, `duration`, `quality`, `departure` or `arrival`. |
| `maxStops` | integer | — | `0` for non-stop only, `1` for at most one connection. Empty means any. |
| `maxDurationHours` | integer | — | Drop trips longer than this door to door. |
| `maxPrice` | integer | — | Only keep fares at or below this amount, in your chosen currency. |
| `includeAirlines` | array | — | Two-character airline codes to restrict the search to. |
| `excludeAirlines` | array | — | Two-character airline codes to leave out. |
| `excludeSelfTransferFares` | boolean | `false` | Keep only trips sold as a single ticket, where the airline is responsible for missed connections. |
| `includeGroundTransport` | boolean | `false` | Allow train and coach legs inside an itinerary. |
| `includeConnectionRisk` | boolean | `true` | Add the self-transfer profile and risk level to every result. |

### Output fields

| Field | What it is |
| --- | --- |
| `price`, `priceFormatted`, `currency` | The fare for the whole party and the currency it was actually served in. |
| `requestedCurrency`, `currencyMismatch`, `currencyMissing` | What you asked for, and whether the fare came back in something else. `currencyMismatch` is `true` or `false` when the served currency is known and `null` when it is not; in that case `currencyMissing` is `true`, `currency` is `null`, and the amount must not be treated as being in your currency. |
| `priceEur` | The same fare in euros, always — a stable column to join or compare on. |
| `priceBeforeDiscount` | The pre-discount fare where one is published. |
| `market`, `source`, `collectedAt` | Which market the search was for, where it came from, and when. |
| `searchId`, `searchOrigin`, `searchDestination` | Which of your searches produced the row. |
| `requestedDepartureDate`, `requestedReturnDate` | The dates you asked for, so flexible searches stay traceable. |
| `tripType`, `cabinClass`, `adults`, `children`, `infants` | What was priced. |
| `originCode`, `originName`, `originCity`, `originCountry` | The actual departure airport — useful when you searched a city-wide code. |
| `destinationCode`, `destinationName`, `destinationCity`, `destinationCountry` | The actual arrival airport. |
| `departureLocal`, `arrivalLocal`, `departureUtc`, `arrivalUtc` | Times in local airport time and in UTC. |
| `returnDepartureLocal`, `returnArrivalLocal` | The inbound leg of a return trip. |
| `durationMin`, `outboundDurationMin`, `inboundDurationMin` | Door-to-door minutes, overall and per direction. |
| `stopCount`, `outboundStopCount`, `inboundStopCount`, `segmentCount` | How many stops and how many legs. |
| `carrierCodes`, `carrierNames`, `flightNumbers`, `hasCodeshare` | Who flies it, and under which numbers. |
| `pnrCount` | How many separate bookings the trip is made of. More than one means separate tickets. |
| `isSelfTransfer`, `isVirtualInterlining`, `isTrueHiddenCity`, `isThrowawayTicket` | The characteristics that make a fare cheap, and riskier. |
| `connectionRiskLevel`, `riskFlags` | An overall label (`NONE`, `LOW`, `MEDIUM`, `HIGH`) and the flags behind it. |
| `shortestLayoverMin`, `stationChanges`, `baggageRechecks`, `protectedConnections` | The connection detail the label is built from. |
| `includedHandBags`, `includedCheckedBags`, `includedPersonalItem` | Baggage included in this fare. |
| `segments` | Every leg: flight number, marketing and operating airline, airports, times, duration, cabin. |
| `layovers` | Every connection: airport, minutes on the ground, airport change, bag re-check, walking distance, whether it is protected. |
| `sellerCode`, `sellerName`, `bookingOptions` | Who is selling it, and every fare product offered for the same itinerary. |
| `bookingUrl` | A direct link that opens this exact trip. |
| `resultsAvailable` | How many itineraries the source says exist for that search, so you know what your limit cut off. |
| `isEstimated` | Always `false`: every value on the row was published by the source, nothing is modelled. |

### Pricing

You pay per result, with one optional extra:

| What you pay for | Price |
| --- | --- |
| **Flight found** — one itinerary written to your dataset | **$2.50 per 1,000** |
| **Connection risk scored** — the self-transfer profile added to a result | **$0.60 per 1,000** |

Worked example: five routes across three dates is fifteen searches. At 50 results each that is 750 flights, so **$1.88**. Leaving connection risk scoring on adds **$0.45**, for **$2.33** in total. Turning scoring off costs nothing extra and simply omits those fields.

A search that runs successfully and finds no flights costs you nothing, because nothing is written. A search the source refuses to complete is reported as failed in the run summary and also produces no rows.

### Limits & what this actor cannot do

- **Fares are a snapshot.** Airline pricing moves continuously; a fare collected now can be gone minutes later.
- **It reports what Kiwi.com publishes to the public. It does not book, hold or reserve anything**, and cannot guarantee a fare is still available when you act on it.
- **The fare is the price shown for the search you asked for.** Taxes, carrier fees and baggage rules vary by airline and fare type, and extras are only reported where they are published.
- **Any one search returns a limited number of itineraries** — up to 1,000, and often far fewer, because that is what the source publishes for that route and date. Each row records how many it says exist.
- **Cheap does not mean protected.** Trips made of separate tickets are labelled as such: if the first leg is late, the next airline owes the traveller nothing beyond what the seller's own guarantee covers. The flags and the risk level are there so you can filter, not a promise about any particular connection.
- **The risk level is a label over published facts**, not a prediction. It is built only from flags and layover lengths that came with the itinerary.
- **Routes are seasonal.** A real route with nothing on the dates you searched is reported as having no flights — that is an answer, not an error, and the run summary counts it separately.
- **A search the source refuses is reported as a failed search, never as "no flights found."** If nothing at all could be searched, the run ends as failed rather than handing you an empty table that looks like a result.
- **Some city-wide codes cannot be searched.** Where a city code has no searchable entry, the actor refuses it and names the airports to use instead, rather than returning an empty list that looks like "no flights".
- **A city name that two airports answer equally well is never settled for you.** `Houston` is refused, naming both IAH and HOU; so is `Frankfurt`, naming FRA and Frankfurt-Hahn. Where the whole city can be searched instead, it is — `Milan`, `Milan, Italy`, `Rome, Italy`, `Paris, France` and `Tokyo, Japan` all search every airport of that city, and the run says so in its warnings. Naming one airport still searches that one airport: `Newark` is EWR and `London Heathrow` is LHR.
- **Overlapping searches repeat a flight on purpose.** If you search both `NYC` and `JFK`, or two date windows that overlap, the same flight can be found by both — it is written once under each search rather than withheld from the second, so no search ever looks emptier than it was. The run summary counts how many flights that affected.
- **A priced result that arrives with no flight legs at all is dropped and counted**, never written as a non-stop flight with no airline, no times and no connection risk.
- **One-way and return trips only.** Multi-city and open-jaw itineraries are not offered, because they are not reliably available here.
- **Fares and availability differ by market and currency.** Results reflect the market you selected, and every row carries it.
- **Speed depends on the size of the job** and on how quickly the source answers; no fixed throughput is promised.
- **Kiwi.com's terms govern automated access.** You are responsible for using the data lawfully and in line with those terms.

### FAQ

**Do I need an account on Kiwi.com?**
No. No account, no login, nothing to sign up for.

**Does it need my password or any personal detail?**
No. You give it routes and dates and nothing else.

**Can I schedule it?**
Yes. Schedule it daily or twice daily and each run appends a fresh snapshot, which is how you build your own price history. Very short intervals are not worth paying for — fares do not move minute to minute.

**Are these fares bookable?**
They were on sale when they were collected, and every row carries a link to that exact trip. Nothing is held or reserved for you, and airlines can change or withdraw a fare at any time.

**Why is one result so much cheaper than the rest?**
Usually because it is a self-connected trip built from separate tickets, or a hidden-city or throwaway fare. Those rows are flagged, scored and easy to exclude with a single switch if you only want ordinary single-ticket fares.

**What happens if a route genuinely has no flights on my dates?**
You get zero rows for that search and the run summary counts it as an empty search — clearly separated from searches that could not be completed, which are counted as failures.

**Can I get fares in my own currency?**
Yes, choose from 37 currencies. Each row also carries the currency it was actually served in and a euro reference price, so you can always tell what a number means.

# Actor input Schema

## `origins` (type: `array`):

Where the trip starts. Use airport codes (JFK, LHR), city-wide codes (NYC, LON) or plain names ("London Heathrow", "Milan"). A name that names a whole city searches every airport of it; a name two airports answer equally well ("Houston", "Frankfurt") is refused with both codes named rather than one being chosen for you. Add several to compare departure points in one run.

## `destinations` (type: `array`):

Where the trip ends. Use airport codes (LAX, CDG), city-wide codes (PAR, MIL) or plain names ("Los Angeles", "Rome"). Same rule as the departure places: a city name searches the whole city, and a name two airports answer equally well is refused with both codes named.

## `tripType` (type: `string`):

One-way, or a return trip. A return trip needs either return dates or a trip length in nights.

## `departureDates` (type: `array`):

Outbound dates in YYYY-MM-DD form. Every date is searched separately, so three dates on one route means three searches. Dates in the past are skipped.

## `returnDates` (type: `array`):

Return dates in YYYY-MM-DD form, for return trips only. Each one is paired with each departure date; pairs that come back before they leave are skipped.

## `nightsMin` (type: `integer`):

An alternative to fixed return dates: let the search pick the return date, as long as the trip lasts at least this many nights.

## `nightsMax` (type: `integer`):

The upper end of the trip length when you let the search pick the return date.

## `dateFlexibilityDays` (type: `integer`):

Widen each date into a window of this many extra days, to catch a cheaper departure a day or two either way. 0 means the exact dates only.

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

Adult travellers. Fares are for the whole party, exactly as the source prices them.

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

Children aged 2-11 travelling in their own seat.

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

Infants under 2 travelling on an adult's lap. Cannot exceed the number of adults.

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

The cabin to price. Higher cabins return far fewer options on most routes.

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

The currency to price fares in. Every row also carries the currency it was actually served in, so you always know what a number means.

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

The market the search is prepared for. Fares and availability can differ between markets, and every row records which one it came from.

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

Language for airline and airport names where the source translates them.

## `maxResultsPerSearch` (type: `integer`):

How many itineraries to keep for each route and date. Raise it for a full picture of a route, lower it when you only need the cheapest few.

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

Which itineraries come back first when a search returns more than you asked for.

## `maxStops` (type: `integer`):

Leave empty for any number of stops. Set 0 for non-stop only, 1 for at most one connection.

## `maxDurationHours` (type: `integer`):

Drop itineraries longer than this door-to-door. Leave empty for no limit.

## `maxPrice` (type: `integer`):

Only return fares at or below this amount, in the currency you selected. Leave empty for no limit.

## `includeAirlines` (type: `array`):

Two-character airline codes to restrict the search to, for example BA or LH. Leave empty for all airlines.

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

Two-character airline codes to leave out of the results.

## `excludeSelfTransferFares` (type: `boolean`):

Leave off to see everything the source sells, including cheaper trips stitched together from separate tickets. Turn on to keep only trips booked as one ticket, where the airline is responsible for missed connections.

## `includeGroundTransport` (type: `boolean`):

Some cheap trips include a train or coach leg between airports. Off by default, so results are flights only.

## `includeConnectionRisk` (type: `boolean`):

Add the self-transfer profile to every result: separate-ticket flags, hidden-city and throwaway flags, the shortest layover, airport changes, bag re-checks, protected connections and an overall risk level. Charged per scored result.

## Actor input object example

```json
{
  "origins": [
    "JFK",
    "EWR"
  ],
  "destinations": [
    "LAX"
  ],
  "tripType": "oneway",
  "departureDates": [
    "2026-09-15",
    "2026-09-16"
  ],
  "returnDates": [
    "2026-09-22"
  ],
  "dateFlexibilityDays": 0,
  "adults": 1,
  "children": 0,
  "infants": 0,
  "cabinClass": "ECONOMY",
  "currency": "USD",
  "market": "US",
  "locale": "en",
  "maxResultsPerSearch": 50,
  "sortBy": "price",
  "includeAirlines": [
    "BA",
    "LH"
  ],
  "excludeAirlines": [
    "FR"
  ],
  "excludeSelfTransferFares": false,
  "includeGroundTransport": false,
  "includeConnectionRisk": true
}
```

# Actor output Schema

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

One row per itinerary: fare, airlines, times, stops, layovers and the booking link.

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

The same results as a table, cheapest first.

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

All results in one CSV file.

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

Counts for this run: results collected, searches read, searches with no flights, and searches 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 = {
    "origins": [
        "JFK"
    ],
    "destinations": [
        "LAX"
    ],
    "departureDates": [
        "2026-09-15"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/kiwi-flight-search-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 = {
    "origins": ["JFK"],
    "destinations": ["LAX"],
    "departureDates": ["2026-09-15"],
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/kiwi-flight-search-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 '{
  "origins": [
    "JFK"
  ],
  "destinations": [
    "LAX"
  ],
  "departureDates": [
    "2026-09-15"
  ]
}' |
apify call apt_marble/kiwi-flight-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/kiwi-flight-search-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/4Er4NnNPGUoEbrp1Z/builds/zaXtJfnGYalup3mzR/openapi.json
