# Flight Price History & Booking Timing (`apt_marble/flight-price-history-booking-timing`) Actor

Is this a good price? For any route: today's cheapest fare against that route's usual price, a low/typical/high verdict, 61 days of daily price history on the very first run, and how far ahead the route is normally cheapest to book — by week, month and weekday.

- **URL**: https://apify.com/apt\_marble/flight-price-history-booking-timing.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 $8.00 / 1,000 route price profile 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

## Flight Price History & Booking Timing

Answers the two questions every traveller and every fare-content page asks:
**is this a good price, and when should I book?** Give it a list of routes and a
departure date, and each route comes back with today's cheapest fare set against
that route's own usual price, a plain verdict — low, typical or high — the last
61 days of daily prices behind that verdict, and how far ahead the route is
normally cheapest to book, week by week, month by month and day by day. The
price history arrives with the very first run: there is no waiting weeks for
your own history to build up. No account and no login are needed — just run it.

### What you can do with it

- **Publish "should I book now?" content** — a verdict, a usual price and a
  61-day price line for every route on your site, refreshed on a schedule.
- **Build a when-to-book guide** — the cheapest week to book ahead, the cheapest
  month to fly and the cheapest day of the week, per route, with the exact
  averages behind each one.
- **Sanity-check a fare you found** — see instantly whether today's cheapest is
  above or below what that route usually costs, and where it sits against the
  lowest and highest price in the 61-day history collected for that same row.
- **Score a route list** — run hundreds of routes and sort by how far today's
  price sits from typical to find the ones worth writing about.
- **Brief a travel-planning or chat product** — hand it the usual price, the
  history and the booking window instead of a single fare with no context.
- **Compare markets honestly** — the same route priced the way travellers in the
  United States, the United Kingdom, Germany or India see it, each row labelled
  with the currency it actually came back in. The when-to-book half is published
  for five markets (US, GB, DE, FR, ES) and every row names the market its
  when-to-book figures describe.

### What you get

One row per route. Real, abridged example:

```json
{
  "routeId": "JFK-LAX-2026-09-30",
  "route": "JFK-LAX",
  "origin": "JFK",
  "destination": "LAX",
  "departureDate": "2026-09-30",
  "tripType": "one_way",
  "cabinClass": "economy",
  "market": "US",
  "source": "google-flights",
  "collectedAt": "2026-08-16T18:00:00.000Z",
  "currency": "USD",
  "currencyFromSource": true,
  "priceVerdict": "typical",
  "currentCheapestPrice": 169,
  "typicalPrice": 156,
  "deltaVsTypical": 13,
  "typicalPriceRangeLow": 100,
  "typicalPriceRangeHigh": 185,
  "hasPriceHistory": true,
  "priceHistoryPoints": 61,
  "priceHistoryStartDate": "2026-06-17",
  "priceHistoryEndDate": "2026-08-16",
  "priceHistoryTimezone": "America/New_York",
  "priceHistoryLowPrice": 126,
  "priceHistoryHighPrice": 178,
  "priceHistory": [
    { "date": "2026-06-17", "epochMs": 1781668800000, "price": 154 },
    { "date": "2026-06-18", "epochMs": 1781755200000, "price": 154 }
  ],
  "bookByWindow": "now, until Sep 16",
  "bookByDate": "Sep 16",
  "flightsFoundOnDate": 32,
  "cheapestFlightPriceOnDate": 169,
  "airlinesOnRoute": [
    { "code": "AA", "name": "American" },
    { "code": "B6", "name": "JetBlue" },
    { "code": "DL", "name": "Delta" }
  ],
  "airlineCount": 13,
  "connectingHubs": [
    { "code": "ORD", "city": "Chicago" },
    { "code": "DFW", "city": "Dallas" }
  ],
  "routePriceLow": 169,
  "routePriceHigh": 992,
  "shortestDurationMin": 350,
  "longestDurationMin": 1484,
  "bookingTimingStatus": "ok",
  "bookingTimingSource": "kayak",
  "bookingTimingMarket": "US",
  "bookingTimingMarketIsRequestedMarket": true,
  "bookingTimingCurrency": "USD",
  "bestBookingWindowWeeks": 3,
  "bestBookingWindowSavingsPercent": 36,
  "cheapestObservedWeeksBefore": 12,
  "cheapestObservedWeeksBeforePrice": 331,
  "priceByWeeksBeforeDeparture": [
    { "weeksBefore": 26, "label": "26", "price": 428 },
    { "weeksBefore": 12, "label": "12", "price": 331 },
    { "weeksBefore": 1, "label": "1", "price": 605 }
  ],
  "priceByWeeksBeforeDepartureBasis": "To calculate weekly average prices, KAYAK takes all prices for each week before departure over the last year for round-trip flights from New York to Los Angeles…",
  "monthlySeasonality": [
    { "monthNumber": 1, "month": "January", "label": "Jan", "price": 294 },
    { "monthNumber": 6, "month": "June", "label": "Jun", "price": 490 }
  ],
  "cheapestMonth": "January",
  "mostExpensiveMonth": "June",
  "priceByWeekday": [
    { "weekdayNumber": 4, "weekday": "Thursday", "label": "Thu", "price": 427.4 },
    { "weekdayNumber": 7, "weekday": "Sunday", "label": "Sun", "price": 555.4 }
  ],
  "cheapestWeekday": "Thursday",
  "mostExpensiveWeekday": "Sunday",
  "cabinClassPrices": [
    { "cabinClass": "economy", "label": "Economy", "price": 739.17 },
    { "cabinClass": "business", "label": "Business", "price": 3774.99 }
  ],
  "recentCheapestPrice": 310,
  "averageFlightDuration": "5h 59m",
  "averageFlightDurationMin": 359
}
```

Alongside the table, every run writes a summary that counts, separately, the
rows produced, the routes read, the routes the sources publish nothing for, the
routes that could not be read at all, the routes rejected before the run started
and the when-to-book profiles added. A route that could not be read is never
counted as a route with no data.

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `routes` | list of text | `JFK-LAX`, `LHR-DXB` | One route per line as two three-letter codes. Airport codes (`JFK`, `LHR`) or city codes covering every airport in a city (`NYC`, `LON`). One row comes back per route. |
| `departureDate` | text | `+30` | The day to price. An exact day (`2026-09-15`) or a number of days from the day the run starts (`+30`). Up to 330 days ahead. |
| `tripLengthDays` | integer | empty | Empty prices a one-way trip. A number of nights prices a return trip departing on your date and coming back that many nights later. Maximum 60. |
| `cabinClass` | select | `economy` | Economy, premium economy, business or first. The verdict, the usual price and the history are all specific to the cabin chosen. |
| `currency` | select | `USD` | The currency to price in. Every row also states the currency that actually came back. |
| `market` | select | `US` | The country you sell into. Fares and availability differ by market. The when-to-book half covers US, GB, DE, FR and ES; choose any other market and those figures come from the US market, and the row says so in `bookingTimingMarket`. |
| `includeBookingTiming` | boolean | `true` | Adds the when-to-book figures: price by week before departure, cheapest month, cheapest weekday and average price by cabin. Published for five markets — US, GB, DE, FR and ES — with every other market answered by the US one and labelled as such on the row. |
| `includeRouteMetadata` | boolean | `true` | Adds the airlines on the route with their names, the connecting airports, and the route's price and journey-time ranges. |
| `maxRoutes` | integer | `50` | Safety cap on how many routes one run covers. Maximum 200. |
| `parallelRoutes` | integer | `4` | How many routes to work on at once. Maximum 6. |

### Output fields

| Field | What it is |
| --- | --- |
| `routeId`, `route`, `origin`, `destination` | The route this row is about. |
| `departureDate`, `returnDate`, `tripType`, `cabinClass`, `market` | What was priced. |
| `currency`, `currencyFromSource` | The currency the fare figures came back in, and whether it was stated by the source rather than assumed. |
| `priceVerdict` | The published verdict for this route today: `low`, `typical` or `high`. Null when none is published. |
| `currentCheapestPrice` | Today's cheapest fare for this route and date. |
| `typicalPrice` | What this route usually costs. |
| `deltaVsTypical` | Today's cheapest minus the usual price. Positive means it is above usual. |
| `typicalPriceRangeLow`, `typicalPriceRangeHigh` | The source's own **usual-price band** for this route — the range it brackets `typicalPrice` with. It is **not** an all-time low and high: on most routes the route's own daily history below runs outside this band, which is normal and is what the band means. |
| `hasPriceHistory`, `priceHistoryPoints` | Whether a price history is published for this route, and how many days of it. |
| `priceHistoryStartDate`, `priceHistoryEndDate` | The span the history covers. |
| `priceHistoryLowPrice`, `priceHistoryHighPrice` | The lowest and highest price in that history — the extremes actually observed over the span above, and the only ones this actor claims. |
| `priceHistoryTimezone` | The zone every history date is stated in: the origin's own. Each day in the series is a day as it was lived at the origin, so a route out of Auckland is dated on Auckland's calendar and one out of New York on New York's. |
| `priceHistory` | The daily series: `date`, `epochMs` and `price` per day. |
| `bookByWindow`, `bookByDate` | The published "book by" advice, when there is one. Often there is not. |
| `flightsFoundOnDate`, `cheapestFlightPriceOnDate` | How many flights were published for that exact date, and the cheapest of them. |
| `airlinesOnRoute`, `airlineCount` | Which airlines fly the route, with their names. |
| `connectingHubs` | The airports flights on this route connect through, with city names. |
| `routePriceLow`, `routePriceHigh` | The route's published price range. |
| `shortestDurationMin`, `longestDurationMin` | The route's journey-time range in minutes. |
| `bookingTimingStatus` | `ok`, `not_published`, `empty`, `failed`, `skipped` or `not_requested` — what happened to the when-to-book look-up. |
| `bookingTimingSource`, `bookingTimingCurrency` | Where the when-to-book figures came from and the currency they are in — which can differ from the fare currency. |
| `bookingTimingMarket`, `bookingTimingMarketIsRequestedMarket` | The market whose travellers the when-to-book figures describe, and whether that is the market you asked for. When-to-book figures exist for five markets — US, GB, DE, FR and ES — so a row asked for in any other market carries US figures here and says so. |
| `bestBookingWindowWeeks`, `bestBookingWindowSavingsPercent` | How many weeks ahead this route is normally best booked, and the saving that implies. |
| `cheapestObservedWeeksBefore`, `cheapestObservedWeeksBeforePrice` | The cheapest week-before-departure in the published curve, and its average price. |
| `priceByWeeksBeforeDeparture` | The full curve: average price for each of the 26 weeks before departure. |
| `monthlySeasonality`, `cheapestMonth`, `mostExpensiveMonth` | Average price by month of the year, and the extremes. |
| `priceByWeekday`, `cheapestWeekday`, `mostExpensiveWeekday` | Average price by day of the week, and the extremes. |
| `cabinClassPrices` | Average price per cabin on this route. |
| `recentCheapestPrice` | The lowest fare the when-to-book source has seen on this route recently. |
| `averageFlightDuration`, `averageFlightDurationMin` | The route's average journey time, as text and in minutes. |
| `...Basis` fields | For every averaged series, the source's own description of what it measured and over what period. Read it before comparing two series — they do not all cover the same window or the same trip type. |
| `collectedAt` | When the row was collected. |

### Pricing

You pay per result, not per minute.

- **Route price profile collected — $8.00 per 1,000 rows.** One row is a whole
  analytics record: the verdict, the usual price, the usual-price band, up to 61
  days of daily prices with their own low and high, and the route's airlines and
  connecting airports.
- **When-to-book figures added — $0.015 each.** Charged only when the figures
  actually exist for that route and are attached to the row. A route with no
  when-to-book data published costs you nothing extra, and neither does a run
  with the option switched off.

**Worked example.** 50 routes with when-to-book figures switched on, and figures
found for 45 of them: 50 rows at $8.00 per 1,000 is $0.10, plus 45 × $0.015 =
$0.68. Total **$0.78** for 50 complete route profiles carrying roughly 3,000
dated prices between them.

### Limits & what this actor cannot do

- **Fares are a snapshot.** The cheapest price is what was published at the
  moment of collection. Airline pricing changes continuously and a fare seen now
  may be gone later.
- **It reports, it does not book.** It cannot hold a seat, buy a ticket or
  guarantee that a fare is still available.
- **The price history is published, not accumulated here.** It is the source's
  own daily series of roughly the last two months. It cannot be extended
  backwards, and it is **not always published**: some perfectly ordinary routes
  come back with flights and prices but no history at all. Those rows say so
  (`hasPriceHistory: false`) rather than showing an invented line.
- **The "book by" advice is often missing.** On most routes tested nothing is
  published, and the field is then null. Null means "not published" — never
  "book any time".
- **The when-to-book figures are averages, each over its own period.** They are
  computed by their source from what travellers there searched for, over
  different periods per series, and some cover return trips while others cover
  one-way. Every series carries its own description; do not compare two series
  without reading them.
- **Two halves, two currencies.** The fare figures and the when-to-book figures
  are priced by their own sources and can arrive in different currencies. Both
  are labelled and nothing is ever converted between them, because a converted
  figure would look precise and be wrong.
- **The when-to-book half covers five markets: US, GB, DE, FR and ES.** Pick any
  other market and the fare half of the row is collected for the market you
  chose, while the when-to-book half is answered by the US market — a different
  travelling public, so the cheapest weekday and the cheapest month themselves
  change, not only the currency. That is never left to be inferred: the row
  carries `bookingTimingMarket` and `bookingTimingMarketIsRequestedMarket`, and
  the run says it in its warnings and its summary. Switch the option off if only
  your own market's figures will do.
- **The usual-price band is not an all-time range.** `typicalPriceRangeLow` and
  `typicalPriceRangeHigh` are the source's own band of usual prices for the
  route — the range around `typicalPrice`. Days outside it are ordinary and
  common, on most routes many of them. For
  a true low and high, use `priceHistoryLowPrice` and `priceHistoryHighPrice`,
  which are measured from the dated series in the same row and cover exactly the
  span that row states.
- **History days are the origin's days.** Every date in the price history is the
  day as it was at the origin, and `priceHistoryTimezone` names that zone.
  Joining two routes from different parts of the world on `date` compares their
  own local days, which is what a daily fare series means — not a single
  worldwide clock.
- **Results differ by market.** A market is a separate inventory, not a currency
  swap. Rows from different markets should not be compared as if they were the
  same numbers.
- **Nothing is invented.** A missing price, month, week or verdict is null. No
  interpolation, no carrying a price from one day to the next, no filling gaps.
- **Dates are limited to 330 days ahead**, and a past date is refused before the
  run starts rather than coming back as an empty answer.
- **Routes are seasonal.** A route with nothing published on your date is
  reported as having nothing — that is an answer, not an error.
- **A route with no published prices at all produces no row.** Small regional
  routes sometimes publish flights but no price of any kind. Those routes are
  named in the run summary as having nothing published, the when-to-book
  look-up is skipped for them, and you are not charged for them — rather than
  handing you a row whose price columns are all empty.
- **A refused read is reported as a failed read**, never as "no data for this
  route". If every route in a run is refused, the run itself fails rather than
  finishing green with an empty table.
- **Speed depends on the size of the job** and on how quickly the sources
  answer. No fixed throughput is promised.
- **The sources' 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 Google Flights or Kayak?**
No. Nothing is signed into and no account of yours is used.

**Does it need my login or password?**
No. There is no credential of any kind in the setup.

**Can I schedule it?**
Yes. A daily or weekly schedule is the normal way to use it — each run appends a
fresh, dated snapshot of every route's verdict and history, which is exactly
what a fare-trend page or a "should I book?" widget needs.

**Are the fares bookable?**
They are what the source published at the moment of collection. Treat them as a
reference price, not a reservation: pricing moves continuously and the actor
cannot hold or buy anything.

**Why does one of my routes have no price history?**
Because none is published for it. It happens on perfectly ordinary routes — the
row still carries the flights found, the route's airlines and, where it exists,
the when-to-book curve, and `hasPriceHistory` tells you plainly which rows have
a series and which do not.

**Why are the when-to-book numbers in a different currency from the fares?**
Because they come from a different source with its own point of sale. Each half
of the row states its own currency and nothing is converted, so the numbers stay
exactly as published. If that currency is not the one you expected, check
`bookingTimingMarket`: it names the market those figures describe, which for any
market outside US, GB, DE, FR and ES is the US one.

**I chose India (or Japan, Canada, Australia…) — whose when-to-book figures do I
get?**
The US market's, because when-to-book figures are published for five markets
only. Every row says so in `bookingTimingMarket`, the run warns about it before
collecting anything, and the fare half of the row is still the market you chose.
These are not the same numbers rescaled — a different set of travellers searched
them — so treat the cheapest weekday and cheapest month as US figures, or turn
the option off.

**What happens if a route cannot be read at all?**
It is counted as a failed read in the run summary, with the route and the reason
listed. It is never mixed in with the routes that genuinely have nothing.

# Actor input Schema

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

One route per line, written as two three-letter codes: JFK-LAX. Use airport codes (JFK, LHR, CDG) or city codes that cover every airport in a city (NYC, LON, PAR). Each route produces one row.

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

The day to price. Write an exact day as 2026-09-15, or a number of days from the day the run starts as +30 — handy for a scheduled report that should always look, say, a month ahead. Fares are published for today and the following 330 days.

## `tripLengthDays` (type: `integer`):

Leave empty to price a one-way trip. Set a number of nights to price a return trip instead: a departure on 15 September with 7 nights comes back on 22 September.

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

Which cabin to price. The price history, the usual price and the verdict are all specific to the cabin you choose.

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

The currency to price in. Every row is stamped with the currency that actually came back, which is not always the one asked for — so you always know what the numbers mean.

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

The country you are selling into. Fares and availability differ by market, so choose the one your travellers buy from and the fare figures come back the way they see them. The when-to-book figures are published for five markets only — United States, United Kingdom, Germany, France and Spain. Choose any other market and those figures describe United States travellers instead; every row names the market they came from in bookingTimingMarket.

## `includeBookingTiming` (type: `boolean`):

Adds how far ahead this route is normally cheapest to book, week by week, plus the cheapest month, the cheapest day of the week and the average price by cabin. Published for the United States, United Kingdom, Germany, France and Spain; for any other market these figures describe United States travellers and the row says so. Charged only for the routes where these figures exist.

## `includeRouteMetadata` (type: `boolean`):

Adds the airlines serving the route with their names, the airports flights connect through, and the route's price and journey-time range.

## `maxRoutes` (type: `integer`):

A safety cap on how many routes one run will cover. Routes beyond it are listed in the run summary rather than quietly dropped.

## `parallelRoutes` (type: `integer`):

How many routes to work on at once. Higher is faster; leave it at the default unless a run is timing out.

## Actor input object example

```json
{
  "routes": [
    "NYC-LON",
    "CDG-FCO"
  ],
  "departureDate": "2026-09-15",
  "tripLengthDays": 7,
  "cabinClass": "economy",
  "currency": "USD",
  "market": "US",
  "includeBookingTiming": true,
  "includeRouteMetadata": true,
  "maxRoutes": 50,
  "parallelRoutes": 4
}
```

# Actor output Schema

## `routeProfiles` (type: `string`):

One row per route, with the price verdict, the price history and the when-to-book figures.

## `routeProfilesCsv` (type: `string`):

The same table as a CSV download.

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

Counts of rows produced, routes read, routes the source publishes nothing for, routes that could not be read, and how many when-to-book profiles were added.

# 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": [
        "JFK-LAX",
        "LHR-DXB"
    ],
    "departureDate": "+30"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/flight-price-history-booking-timing").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": [
        "JFK-LAX",
        "LHR-DXB",
    ],
    "departureDate": "+30",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/flight-price-history-booking-timing").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": [
    "JFK-LAX",
    "LHR-DXB"
  ],
  "departureDate": "+30"
}' |
apify call apt_marble/flight-price-history-booking-timing --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/flight-price-history-booking-timing"
        }
    }
}

```

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/pWLTeczbY8MkDeVFU/builds/tu06Q2x7e40d28GXW/openapi.json
