Flight Fare History API: is today a good day to book this? avatar

Flight Fare History API: is today a good day to book this?

Pricing

from $60.00 / 1,000 fare observation stored and scoreds

Go to Apify Store
Flight Fare History API: is today a good day to book this?

Flight Fare History API: is today a good day to book this?

Is today a good day to book? Scores today's cheapest fare against the price history it builds for your route and answers BOOK_NOW, GOOD_PRICE, TYPICAL or WAIT. It answers on the first run. $0.01 a run plus $0.06 a fare scored; the flight search itself is billed to you by the scraper.

Pricing

from $60.00 / 1,000 fare observation stored and scoreds

Rating

0.0

(0)

Developer

FrameProbe

FrameProbe

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

3 days ago

Last modified

Share

Give it a route. Get back today's cheapest fare with a plain answer beside it: book now, good price, typical, or wait, scored against the prices this route has actually been quoted before.

departDates is what builds the history. The history is kept per departure date. List the dates of your trip in departDates and run it on a schedule: each run adds today's fare to each date's history, and once a date holds 3 stored prices the verdict is scored against them. Runs less than an hour apart add one price, not two, and a search that finds no price adds nothing.

Actor idframeprobe/flight-fare-history-api
Minimal input{"origin": "LAX", "destination": "JFK"}
Cost$0.01 per run, plus $0.06 per itinerary that produced a price and a stored history entry. An itinerary that produced no price is not charged. Separately, the flight search itself runs on your own Apify account at the scraper's price, about $0.01 to $0.025 a search, and that is most of the bill early on: a run buys about 7 searches until that itinerary holds 3 stored observations, then 1 a run after that.
OutputOne row per itinerary: price, currency, carrier, stops, verdict (BOOK_NOW, GOOD_PRICE, TYPICAL, WAIT, BASELINE or INSUFFICIENT_HISTORY), trend, confidence, a reason sentence, and the yardstick the call was measured against.

Click Try for free. The input form arrives with LAX to JFK already filled in. The first run answers, rather than telling you to come back tomorrow: it prices a spread of nearby departure dates on the same route so there is something to score against on day one.

  • Decide whether to book a flight today or wait another week.
  • Watch a route you care about on a schedule and get told when the price is unusually low.
  • Build your own fare history for routes you fly often, kept in your own Apify account.

With no dates, it still builds a history. It watches one Tuesday departure 45 to 72 days out, and that date stays the same for 28 days in a row. Schedule it daily and the same flight collects an observation every day. The first three runs on a date also price 6 nearby dates as a yardstick, which the flight scraper bills you for. From the fourth run it answers from its own history and buys one search. After 28 days it moves to the next date and that cycle starts again. Name your own departDates to watch a specific trip for longer.

Every flight scraper tells you what a fare costs today. None of them tells you whether today is a good day to book, because none of them remembers yesterday. This one keeps a price history per itinerary in your own Apify key-value store and scores each new observation against it.

POST /acts/~flight-fare-history-api/runs
{ "origin": "LAX", "destination": "JFK" }
{
"rowType": "observation",
"itinerary": "LAX-JFK 2026-11-02, economy",
"price": 412.0,
"currency": "USD",
"carrier": "Delta",
"stops": 0,
"verdict": "BOOK_NOW",
"basis": "history",
"trend": "FALLING",
"confidence": "high",
"reason": "412 is the cheapest seen across 14 observations (cheapest 412, median 505).",
"basisReference": 505.0,
"basisCheapest": 412.0,
"basisCount": 14,
"pctVsBasis": -18.42,
"routeDatesPriced": 0,
"deltaVsPrevious": -38.0,
"deltaVsMedian": -93.0,
"pctVsMedian": -18.42,
"pctVsCheapestSeen": 0.0,
"observationsUsed": 14,
"historyCount": 15,
"historyFirstSeenAt": "2026-08-22T09:00:04+00:00",
"historyCheapest": 412.0,
"historyMedian": 505.0,
"notes": []
}

Built for pipelines

  • One job. Route in, verdict out. No modes, no flags that change the response shape.
  • Two required fields. origin and destination, and that is the whole call. With no dates it watches one departure 45 to 72 days out that holds still for 28 days, so a scheduled request builds a history and never goes stale.
  • Same keys on every row, including rows that failed. Nulls, never absent keys, so you never branch on which fields exist.
  • Idempotent. Two runs an hour apart do not double-count: an observation inside the one-hour window is reported and not appended.
  • Deterministic ordering. Rows come back in the order you listed the dates, then any route-baseline samples, summary last. A diff between two runs shows price changes, not row shuffling.

The verdicts

VerdictMeans
BOOK_NOWAt the cheapest seen, materially below the median, and in the cheapest quarter. Needs 5+ prior observations of this itinerary. basis: history only.
GOOD_PRICEMeaningfully below the baseline, but not at the floor.
TYPICALWithin 3% of the baseline. Normal.
WAITAbove the baseline; it has been cheaper.
INSUFFICIENT_HISTORYFewer than 3 prior observations and nothing else to compare with. The arithmetic is returned; the label is withheld.
BASELINEThe first observation for this itinerary, with nothing else to compare it with.

trend (FALLING / RISING / FLAT / UNKNOWN) is reported separately, because "expensive but falling" and "expensive and rising" are different decisions. It needs history, so it is UNKNOWN until you have two observations.

What it compares against, and how much that is worth

Every row names its basis. A verdict from a month of history is a stronger claim than one from a spread of dates read in the same minute, and basis is how a pipeline tells them apart without parsing English. confidence is capped by the basis, so a weaker yardstick can never report high.

basisThe question it answersMax confidenceCosts
historyIs this cheap compared with what you have been offered for this trip since you started watching?highnothing extra
routeOf the departure dates you could fly, is this one cheap?mediumone upstream search per extra date
upstream_typicalIs this below the upstream's own typical-price figure for the route?lownothing extra
noneNothing was available. BASELINE or INSUFFICIENT_HISTORY.nonenothing

The default scraper, memo23/google-flights-scraper, does not publish a typical price (checked against a live run). On it the ladder is history then route then none. The third tier only fires if you point upstreamActor at a scraper that does publish one.

BOOK_NOW is reachable only from history. It asserts a floor across time, and a set of prices all read in the same minute has no time axis to find a floor in. A cross-section can say this date is the cheapest of the dates you looked at; it cannot say today is the cheapest day to buy it.

Your first run is not wasted

A history-based tool has a real cold start: on day one there is no history. Rather than hand you INSUFFICIENT_HISTORY and an invitation to come back in a fortnight, a run whose itineraries have too little history prices a spread of other departure dates on the same route and scores today's fare against those instead.

How often that succeeds, measured rather than promised. The cross-section needs at least four of those extra dates to return a price, so it depends on the upstream scraper answering.

Measured 2026-09-07, against the default upstream: 58 searches, 0 empty. Four first runs on four fresh routes, each with no stored history:

First runResult, measured 2026-09-07
Produced a price4 of 4
Produced a route verdict, basis: route4 of 4
At medium confidence, all six baseline dates priced4 of 4

An earlier default upstream returned empty on ~39% of searches, which put the same figure at about 34%. That is why every row is labelled rather than assumed: the numbers above move with the upstream you point upstreamActor at, and a run tells you which case it got instead of leaving you to infer it.

This Actor never promises a verdict. It promises a labelled outcome. Every row states the basis it used and the confidence that basis supports, or says plainly that it had neither, so a pipeline can branch on the label instead of trusting a number.

This is a real first run, not an illustration. Thanksgiving week out of LAX:

{ "origin": "LAX", "destination": "JFK", "departDates": ["2026-11-23"] }
{
"verdict": "WAIT",
"basis": "route",
"confidence": "low",
"price": 254.0,
"carrier": "JetBlue",
"basisReference": 202.0,
"basisCheapest": 199.0,
"basisCount": 4,
"pctVsBasis": 25.74,
"reason": "254 is 25.7% above the 202 median of 4 other departure dates on this route (cheapest 199). This compares dates, not days: it places this date among the ones you could fly, and does not say whether today is a good day to book it.",
"historyCount": 1
}

The series still starts building on that same run, and as soon as it reaches three observations the basis switches to history on its own. Nothing to change, nothing to re-run.

Note the low, and note that two of the six baseline dates are missing from that count. The upstream returned nothing for two of them, so the cross-section was built from four dates instead of six and the confidence was lowered to match. Nothing here rounds up: a run reports the width of the yardstick it actually got.

medium requires all six baseline dates to return a price. Against the default upstream that is the ordinary result - four of four measured first runs reached it on 2026-09-07. Against a less reliable scraper it is rare, and the row says low rather than rounding up. The example above, recorded against the older default, is what a partial cross-section looks like.

What it costs, plainly. Each extra date is one more upstream search billed to your account, exactly like a watched date. It is not charged this Actor's fare-observation event.

  • routeBaselineDates defaults to 6 and is capped at 12. A one-date first run therefore costs 7 upstream searches instead of one. Lower it to 4 for a cheaper first run, or to 0 to skip the cross-section entirely.
  • medium needs all six baseline dates to return a price, which the default upstream managed on four of four measured first runs. A less reliable scraper yields low, which is reported rather than rounded up.
  • Spending stops as soon as the run has priced 7 dates in total, the width a date needs to be scored at medium, since a date is never counted in its own baseline. List seven departDates yourself and the cross-section is free.
  • Nothing is bought when your history is already long enough to answer, when you passed a datasetId, or when routeBaselineDates is 0.
  • The extra dates come back as rowType: "routeSample" rows with their prices, so you can check the median by hand rather than take it on trust. They carry no verdict and are never charged.

Dates are chosen deterministically, in whole weeks either side of your date (-7, +7, -14, +14…), so day of week is held constant: a Monday is compared with Mondays. Past dates are skipped, not clamped.

How it differs from the "typical price" badge

Google's own insight compares a fare with an aggregate across all travellers on that route. It is used here only as the last-resort upstream_typical basis, with confidence capped at low, because we cannot see how it was computed and it cannot establish a floor. The default scraper does not expose it at all, so in practice this Actor never re-serves it. The history basis compares a fare with your observed series for one exact itinerary shape: same cabin, same passenger count, same stop and carrier filters. Those diverge exactly when it matters: a route can be expensive in general and at its own floor today.

What you get on day 1, day 4, and day 30

This is a history product, so the honest thing to say is what it can and cannot do yet. The verdict gets stronger as your series grows, and every row tells you which rung it is on.

The series is one departure date. Put the dates you want watched in departDates and keep them the same from run to run; changing a date starts a new history. With no dates, the Actor picks one on a fixed 28-day schedule, so it holds for up to 28 days and can move sooner if you start late in a cycle.

RunWhat it can compare againstVerdict you can getconfidence
Day 1other departure dates priced in the same runGOOD_PRICE / TYPICAL / WAIT, basis: routelow to medium
Days 2-3still the cross-section; your series is filling insame, basis: routelow to medium
Day 4your own series takes over, 3 observationssame verdicts, basis: historylow
Day 65 observationsBOOK_NOW becomes reachablemedium
Day 1110 observationsfull ladder, trend meaningfulhigh
Day 30+a month of your own pricesthe verdict this product exists forhigh

You are never guessing about which one you got. basis and confidence are fields, not prose, so a pipeline can refuse to act on a low and act on a high without parsing English.

What 60 days costs, as one number

Under $5 to watch one itinerary daily for 60 days. About $4.20 in this Actor's events, plus about $0.65 of upstream searches on your own account. That is the whole commitment, and it is bounded: maxItineraries and routeBaselineDates cap the upstream, and maxTotalChargeUsd caps this Actor per run.

The history is yours and it outlives us. Every observation is written to a named key-value store in your Apify account. Stop running this Actor tomorrow and the series is still there, still readable, still exportable, still queryable by anything else you own. You are not renting access to your own data, and you are not locked in - which is the point of putting it in your account rather than ours.

A price drop is not always a saving

Every scraper on this shelf will tell you a fare went down. This one will tell you when the fare went down because the flight got worse.

A fare that drops 15% while the itinerary gains four hours and a connection is not a bargain, it is a different product wearing the same route. When today's cheapest differs materially from what your series has been tracking, the verdict is PRODUCT_CHANGED and it replaces the price verdict rather than sitting beside it - because a pipeline branching on GOOD_PRICE would otherwise book a downgrade.

The honest limit: this compares today against your stored history, and observations stored before 2026-09-08 kept the price, carrier and stop count but not the duration or routing. A series that spans that date will start producing PRODUCT_CHANGED only as the newer observations accumulate. Nothing is inferred about the older ones.

What it costs

EventPrice
Actor start$0.01, charged only after your input validates
Fare observation stored and scored$0.06 per itinerary

Repriced 2026-09-08, and what changed with it. This Actor used to charge $0.005 per observation, priced against the per-row rate of the scrapers it sits beside. It is not a scraper and the comparison was wrong: it now stores the complete upstream record for every observation, which is what makes PRODUCT_CHANGED possible, and it will not tell you a fare is cheap when the itinerary has quietly got worse. You are not paying more for the same verdict.

An itinerary that produced nothing is not charged. A failed upstream search, an empty result, or rows with no readable price all appear in the dataset with the reason and cost you nothing beyond the start event. Route-baseline samples are not charged at all.

What a run actually costs you, both halves

Most of the bill is not ours. These are worked examples at the default maxItineraries of 20, where one upstream search costs about $0.025 on memo23/google-flights-scraper. Your own numbers will move with the scraper you name and the results a route returns.

RunUpstream searchesThis ActorUpstream, to youAll-in
Runs 1 to 3, one date (each buys a 6-date route baseline)7$0.07~$0.175~$0.245
Runs 1 to 3, seven dates (baseline comes free)7$0.43~$0.175~$0.605
Run 4 onward, one date, once your history can answer1$0.07~$0.025~$0.095

Three things worth reading off that table. The early runs on a route are the expensive ones, because each one buys the cross-section that lets it answer at all. That is runs one to three, not just run one: the baseline is bought whenever the itinerary still holds fewer than three stored observations, which is the same number a verdict needs. From run four on, that itinerary costs about ten cents. And listing seven dates instead of one costs you nothing extra upstream while producing seven series instead of one, which is the cheapest way to use this Actor.

The upstream search is billed separately, to you. This Actor does not scrape Google. It runs automation-lab/google-flights-scraper (or whichever scraper you name) as a sub-run on your own account, at that Actor's price. memo23/google-flights-scraper is also supported directly; the two use different input field names and the translation is done for you. Only memo23 filters by airline, so airlines requires it - the run is refused rather than storing an observation labelled with a carrier filter that was never applied. maxStops works with either, applied by this Actor when the upstream cannot. Watching 30 dates means 30 upstream searches, which is why dateWindowDays is capped at 60 and a run over 60 itineraries is refused rather than billed. Route-baseline dates are searches too, capped separately by routeBaselineDates.

Already ran a scraper yourself? Pass datasetId and nothing is fetched at all.

Your history is yours

Series are stored in a named key-value store in your account (fare-history by default), one JSON record per itinerary. Read it, export it, point another tool at it, or delete it. None of that needs this Actor. Records are append-only and capped at 400 observations, dropping the oldest.

A series never mixes currencies. If the upstream returns a different currency from the stored series, today's price is reported and the history is left untouched, because a median computed across USD and EUR describes nothing. The same rule applies to the cross-date baseline: dates priced in another currency are left out of it rather than converted.

Unreadable history restarts rather than failing. If a stored record cannot be parsed, the run tells you so on the row and starts a fresh series. It will not fail your run, and it will not pretend the history was fine.

Route-baseline dates are not stored. They are priced to answer today's question and discarded. Only the dates you asked to watch become series, so a probe can never quietly inflate your history or your bill.

Limits, stated plainly

  • The cross-date baseline answers a different question from the history one. It tells you where a date sits among the dates you could fly. It cannot tell you whether the fare is falling, and it will never say BOOK_NOW. Read basis before acting on verdict.
  • A cross-section needs at least 4 other priced dates. Below that it is a couple of anecdotes, and the run falls back to the upstream figure or to BASELINE rather than dressing them up.
  • Three prior observations before a history verdict, five before BOOK_NOW.
  • One observation per run per itinerary, and observations closer together than an hour are not appended. Fares do not move on a timescale you can act on faster than that.
  • The series follows the cheapest qualifying itinerary, not Google's "best" tag, whose definition mixes price with duration and can change without notice. Reproducibility beats a weighting we cannot see. On a live LAX-JFK search the best fares were $219 and $244 while the cheapest was $205: the gap is the point.
  • It depends on an upstream scraper. If that Actor breaks or gets blocked, this one reports the failure per itinerary and charges nothing for it, but it cannot invent a price.