# True Cost of Flying — Baggage Fee Scraper (`apt_marble/true-cost-baggage-fee-scraper`) Actor

Find out what a Kiwi.com fare really costs once you add a bag. Every result comes back with its included allowance, the price of each extra cabin and checked bag, the weight and size limits, and a combined fare-plus-bags total ranked cheapest first. 37 currencies, no account needed.

- **URL**: https://apify.com/apt\_marble/true-cost-baggage-fee-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 $3.00 / 1,000 fare with baggage costs 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/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

## True Cost of Flying — Baggage Fee Scraper

The cheapest fare on the screen is very often not the cheapest trip. This actor searches Kiwi.com for a route and a date, and returns every fare **with its baggage terms attached**: how many cabin and checked bags the fare already includes, what each extra bag costs, how heavy and how large that bag is allowed to be, and the combined fare-plus-bags total — ranked cheapest first on the total rather than on the sticker price. On a real London → Dubai search, the €505 fare came third once a bag was added and a €523 fare came first, because the bags cost €217 on one and €144 on the other. That is the answer this actor exists to give, and it is normally buried several screens into an airline's own booking flow.

### What you can do with it

- **Answer "which fare is actually cheapest with a bag."** Every result carries a combined total and a rank, so the reordering is done for you.
- **Build a baggage-fee reference for a route, an airline or a whole market.** Real prices, real weight limits and real size limits, per fare, not a policy page you have to interpret.
- **Compare fare products of the same flight.** The same departure is usually sold at more than one price with identical baggage terms; every one of them is returned and the cheapest is marked.
- **Show travellers a true price in your own product.** Fare, bag cost and total in the currency of the market you are selling into.
- **Model ancillary revenue.** How much of a trip's price is the bag, by carrier, by route, by cabin, over time.
- **Flag trips where the bag is handled twice.** Self-connected itineraries are marked, along with whether the bag has to be collected and re-checked on the way.

### What you get

One row per fare product. Real example, abridged (the long identifier 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",
  "fareProductCode": "KIWI-BASIC",
  "isCheapestFareOption": true,
  "fareOptionCount": 2,
  "basePrice": 176,
  "marketingCarrierName": "JetBlue Airways",
  "flightNumbers": ["B6123"],
  "departureLocal": "2026-09-15T06:00:00",
  "arrivalLocal": "2026-09-15T08:59:00",
  "stopCount": 0,
  "durationMin": 359,
  "baggageDataPublished": true,
  "includedPersonalItem": 1,
  "includedHandBags": 1,
  "includedCheckedBags": 0,
  "handBagPrice1": 0,
  "checkedBagPrice1": 52.5,
  "checkedBagPrice2": 127.5,
  "checkedBagWeight": 22,
  "checkedBagWeightUnit": "KG",
  "checkedBagDimensionsSum": 157,
  "handBagDimensions": "55 x 22 x 35 CM",
  "checkedBagTiers": [
    { "bags": 1, "price": 52.5, "currency": "USD", "includedInFare": 0, "weight": 22, "weightUnit": "KG", "dimensionsSum": 157, "dimensionsUnit": "CM" },
    { "bags": 2, "price": 127.5, "currency": "USD", "includedInFare": 0, "weight": 22, "weightUnit": "KG", "dimensionsSum": 157, "dimensionsUnit": "CM" }
  ],
  "handBagsPriced": 0,
  "checkedBagsPriced": 1,
  "checkedBagFee": 52.5,
  "baggageFeeTotal": 52.5,
  "allInPrice": 228.5,
  "rankByAllInPrice": 1,
  "isSelfTransfer": false,
  "baggageRecheckRequired": false,
  "bookingUrl": "https://www.kiwi.com/en/booking/?…",
  "isEstimated": false
}
```

### 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`). Several are allowed. |
| `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. |
| `bagsToAdd` | string | `checked` | Which bags to price on top of every fare: `none`, `hand`, `checked` or `both`. |
| `handBagsToAdd` | integer | — | Optional exact number of cabin bags to price (0–2), overriding the choice above. |
| `checkedBagsToAdd` | integer | — | Optional exact number of checked bags to price (0–3), overriding the choice above. |
| `cabinClass` | string | `ECONOMY` | `ECONOMY`, `PREMIUM_ECONOMY`, `BUSINESS` or `FIRST_CLASS`. Allowances differ sharply by cabin. |
| `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. |
| `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. |
| `maxResultsPerSearch` | integer | `30` | Flights to keep per route and date, up to 1,000. Each can produce more than one row. |
| `fareOptions` | string | `all` | Keep every fare product a flight is sold as, or only the cheapest. |
| `maxStops` | integer | — | Optional ceiling on stops (0–3). Empty means no limit. |
| `includeAllInPrice` | boolean | `true` | Work out the fare-plus-bags total and rank each search on it. |
| `onlyFaresWithBaggagePrices` | boolean | `false` | Drop fares whose baggage terms are not published, instead of returning them with empty columns. |
| `excludeSelfTransferFares` | boolean | `false` | Leave out trips stitched together from separate tickets. |

### Output fields

| Field | Type | What it is |
| --- | --- | --- |
| `basePrice` | number | The fare on its own, before any bag is added. |
| `currency` | string | null | The currency this row was actually served in. `null` when the fare arrived without one. |
| `requestedCurrency` / `currencyMismatch` | string / boolean | null | What you asked for, and whether it differs from what arrived. `null` means the row's own currency is missing, so the comparison cannot be made — never read that as "no mismatch". |
| `currencyMissing` | boolean | `true` when the amounts on this row arrived with no currency code at all. They are still exactly what the source served, but nothing labels them, so they are never treated as the currency you asked for. |
| `allInPrice` | number | null | Fare plus the bags you asked to price. `null` when the fare publishes no price for that many bags. |
| `rankByAllInPrice` | number | null | Position within its own search, cheapest total first. Ties share a rank; an unknown total is left unranked. |
| `baggageFeeTotal` / `handBagFee` / `checkedBagFee` | number | null | What the bags you asked for cost on this fare. |
| `includedHandBags` / `includedCheckedBags` / `includedPersonalItem` | number | null | What the fare already includes. |
| `handBagPrice1` / `handBagPrice2` | number | null | Total price for that many cabin bags. |
| `checkedBagPrice1` / `checkedBagPrice2` / `checkedBagPrice3` | number | null | Total price for that many checked bags. |
| `handBagTiers` / `checkedBagTiers` / `personalItemTiers` | array | Every published tier: how many bags, the price, whether it is already in the fare, weight, each side and the sum of the three sides, with units. |
| `handBagDimensions` | string | null | Cabin bag size as length x width x height with its unit. |
| `checkedBagWeight` / `checkedBagWeightUnit` | number / string | Weight allowance for a checked bag and the unit it is stated in. |
| `checkedBagDimensionsSum` / `checkedBagDimensionsUnit` | number / string | The published sum of the three sides, and its unit. |
| `baggageDataPublished` | boolean | `false` means the source published no baggage terms for this fare — not that no bags are allowed. |
| `checkedBagsAvailable` | boolean | null | Whether a checked bag can be bought on this fare at all. |
| `fareProductCode` / `fareProductName` | string | Which fare product this row is. The same flight is often sold as several. |
| `isCheapestFareOption` / `fareOptionCount` | boolean / number | Whether this is the cheapest product for that flight, and how many exist. |
| `marketingCarrier` / `marketingCarrierName` / `carrierCodes` / `carrierNames` | string / array | The airlines on the trip. |
| `flightNumbers` | array | Real flight numbers, in order. |
| `originCode` / `originName` / `destinationCode` / `destinationName` | string | Where it actually leaves from and arrives at. |
| `departureLocal` / `arrivalLocal` / `returnDepartureLocal` | string | Local times. |
| `stopCount` / `segmentCount` | number | null | Shape of the trip. Both are `null`, never `0`, when the flights behind the fare did not arrive — a trip with no flights in it does not exist, and `0` would read as "non-stop". |
| `routeDataPublished` | boolean | `false` when the fare arrived without its flights. Such a row still carries a real price, but its stops, flight numbers, airports and times are unknown rather than empty. |
| `durationMin` | number | Length of the trip. |
| `outboundDurationMin` / `inboundDurationMin` | number | null | Each direction of a return trip. |
| `isSelfTransfer` / `pnrCount` | boolean / number | Whether the trip is stitched from separate tickets, and how many. |
| `baggageRecheckRequired` | boolean | null | Whether the bag has to be collected and re-checked on the way. `false` only on a trip published as non-stop; `null` when there is a connection whose terms were not published, or when the flights did not arrive at all. |
| `bookingUrl` | string | A direct link to that exact fare on Kiwi.com. |
| `searchId` / `searchOrigin` / `searchDestination` / `requestedDepartureDate` / `requestedReturnDate` | string | Which search produced the row. |
| `market` / `collectedAt` | string | The market chosen, and when the row was collected. |
| `isEstimated` | boolean | Always `false`. Every number on the row was published by the source. |

### Pricing

You pay per result, not per run.

| What you pay for | Price |
| --- | --- |
| Fare with baggage costs found | **$3.00 per 1,000 rows** |
| All-in price worked out | **$0.80 per 1,000 rows** |

The first is charged for every row delivered. The second is charged only when a combined fare-plus-bags total was actually produced — a fare that publishes no price for the bags you asked about is delivered with an empty total and costs you nothing extra. Choose **no bags** and the total is simply the fare itself, so the second charge is not made at all. Switch the all-in price off and only the first charge applies.

**Worked example.** One route on one date, keeping 30 flights, most of them sold as two fare products, gives roughly 60 rows: 60 × $0.0030 = **$0.18**, plus $0.0008 for each row that got a total — say 55 of them, **$0.044**. About **22 cents** for that search. Twenty routes across ten dates in one run, at the same shape, is around **$44**.

### Limits & what this actor cannot do

- **Fares and bag prices are a snapshot.** Airline pricing changes continuously, and a fare or a fee seen now may be different minutes later.
- **It reports what the source publishes to the public.** It does not hold seats, does not book, and cannot guarantee a fare or a bag price is still available when you go to buy.
- **Nothing is estimated.** When a fare publishes no price for the number of bags you asked about, the total comes back empty with the reason attached — it is never filled in from another fare, another tier or another date. Second and third checked bags in particular are often not published at all.
- **`baggageDataPublished: false` means unknown, not zero.** Some fares arrive with no baggage terms; those rows carry empty baggage columns rather than a guess.
- **Bag prices are what the seller charges for that fare.** An airline may price the same bag differently at the airport, at check-in, or through its own site, and oversized or overweight bags are charged separately.
- **Weight and size limits are published in the units the source states them in.** They are passed through as given, with the unit on every value, and are never converted.
- **Results differ by market and currency.** Fares, fare products and bag prices are not the same in every market; each row records the market chosen and the currency actually served.
- **The source publishes a limited number of results for any one search**, so a very broad search returns what it makes available rather than an unlimited list.
- **Routes and schedules are seasonal.** A route with no flights on the dates you searched is reported as having none, and that is separated in the run summary from a search that could not be completed.
- **A search the source refuses to complete is reported as a failed search, never as "no fares found".** That includes an answer that contradicts itself — no fares delivered while the same answer states how many were available — which is counted as a search that could not be completed rather than as a route with no flights. If every search in a run fails, the run itself fails rather than finishing green with an empty table.
- **A fare that arrives incomplete says so on the row.** One that arrives without its flights carries `routeDataPublished: false` and an unknown stop count instead of reading as a non-stop; one that arrives with no currency code carries `currencyMissing: true` and an unknown mismatch instead of being treated as the currency you asked for.
- **Speed depends on the size of the job** and on the source's own response times; no fixed throughput 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 an account on Kiwi.com?**
No. Nothing is signed in to and nothing is bought.

**Does it need my login or password?**
No. There is nothing to configure beyond the search itself.

**Are these fares and bag prices bookable?**
They are what the source published at the moment of collection, and every row carries a direct link to that exact fare. Prices move, so treat a row as a snapshot rather than a guarantee.

**Why does the same flight appear more than once?**
Because it is genuinely sold as more than one fare product, at different prices, usually with the same baggage terms. Both are returned, and `isCheapestFareOption` marks the cheaper one. Set `fareOptions` to `cheapest` to keep only that one.

**Why is `allInPrice` sometimes empty?**
Because that fare publishes no price for the number of bags you asked about. Filling it in would mean inventing a fee, so the field is left empty and `allInPriceUnavailableReason` says why.

**Why does the cheapest fare sometimes rank third?**
That is the point of the actor. A cheaper fare with an expensive bag can cost more than a dearer fare that includes one. `rankByAllInPrice` orders each search on the total, not on the sticker price.

**Can I schedule it?**
Yes. Run it on a schedule to build your own history of fares and baggage prices for the routes you care about.

# 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", "Oslo"). 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", "Nice"). Add several to compare destinations in one run.

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

One-way, or a return trip. A return trip needs at least one return date.

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

## `bagsToAdd` (type: `string`):

Which bags to add on top of every fare. The trip is always searched without bags first, so the fare and the bag fees stay separate and you can see both.

## `handBagsToAdd` (type: `integer`):

Optional. Overrides the choice above with an exact number of cabin bags to price. Leave empty to use the choice above.

## `checkedBagsToAdd` (type: `integer`):

Optional. Overrides the choice above with an exact number of checked bags to price. Leave empty to use the choice above.

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

Travel class to search. Baggage allowances and fees differ sharply between cabins, so this changes the answer.

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

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

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

Children aged 2 to 11 travelling in their own seat.

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

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

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

The currency to price fares and bags in. Every row also records 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 differ between markets; the market you pick is recorded on every row.

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

How many itineraries to keep for each route and date, cheapest first. Each one can produce more than one row, because the same flight is often sold as several fare products.

## `fareOptions` (type: `string`):

The same flight is usually sold as more than one fare product at different prices with the same baggage terms. Keep them all to compare, or keep only the cheapest.

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

Optional. Keep only trips with at most this many stops. Leave empty for no limit.

## `includeAllInPrice` (type: `boolean`):

Work out the combined fare-plus-bags total for every fare and rank each search cheapest first on that total. If you choose "no bags", the total is simply the fare and the extra all-in charge is not made. Turn it off to collect the published allowances and prices only.

## `onlyFaresWithBaggagePrices` (type: `boolean`):

Drop any fare whose baggage terms the source does not publish, instead of returning it with those columns empty.

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

Leave out trips stitched together from separate tickets, where a traveller may have to collect and re-check a bag at the connection.

## Actor input object example

```json
{
  "origins": [
    "JFK",
    "EWR"
  ],
  "destinations": [
    "LAX"
  ],
  "tripType": "oneway",
  "departureDates": [
    "2026-09-15",
    "2026-09-16"
  ],
  "returnDates": [
    "2026-09-22"
  ],
  "bagsToAdd": "checked",
  "cabinClass": "ECONOMY",
  "adults": 1,
  "children": 0,
  "infants": 0,
  "currency": "USD",
  "market": "US",
  "maxResultsPerSearch": 30,
  "fareOptions": "all",
  "includeAllInPrice": true,
  "onlyFaresWithBaggagePrices": false,
  "excludeSelfTransferFares": false
}
```

# Actor output Schema

## `fares` (type: `string`):

One row per fare product: the fare, the included allowance, every priced bag tier and the all-in total.

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

The same results as a table, cheapest all-in first.

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

All results in one CSV file.

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

Counts for this run: fares collected, searches read, searches with no flights, searches that could not be completed, and fares whose baggage terms the source did not publish.

# 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/true-cost-baggage-fee-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/true-cost-baggage-fee-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/true-cost-baggage-fee-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/true-cost-baggage-fee-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/YQlRPMSp5QnECBSkE/builds/t7AFm29v5sB15BFbJ/openapi.json
