Flight Fare History API: is today a good day to book this?
Pricing
from $60.00 / 1,000 fare observation stored and scoreds
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
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
3 days ago
Last modified
Categories
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 id | frameprobe/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. |
| Output | One 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
departDatesto 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.
originanddestination, 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
| Verdict | Means |
|---|---|
BOOK_NOW | At the cheapest seen, materially below the median, and in the cheapest quarter. Needs 5+ prior observations of this itinerary. basis: history only. |
GOOD_PRICE | Meaningfully below the baseline, but not at the floor. |
TYPICAL | Within 3% of the baseline. Normal. |
WAIT | Above the baseline; it has been cheaper. |
INSUFFICIENT_HISTORY | Fewer than 3 prior observations and nothing else to compare with. The arithmetic is returned; the label is withheld. |
BASELINE | The 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.
basis | The question it answers | Max confidence | Costs |
|---|---|---|---|
history | Is this cheap compared with what you have been offered for this trip since you started watching? | high | nothing extra |
route | Of the departure dates you could fly, is this one cheap? | medium | one upstream search per extra date |
upstream_typical | Is this below the upstream's own typical-price figure for the route? | low | nothing extra |
none | Nothing was available. BASELINE or INSUFFICIENT_HISTORY. | none | nothing |
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 run | Result, measured 2026-09-07 |
|---|---|
| Produced a price | 4 of 4 |
Produced a route verdict, basis: route | 4 of 4 |
At medium confidence, all six baseline dates priced | 4 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.
routeBaselineDatesdefaults 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.mediumneeds all six baseline dates to return a price, which the default upstream managed on four of four measured first runs. A less reliable scraper yieldslow, 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 sevendepartDatesyourself and the cross-section is free. - Nothing is bought when your history is already long enough to answer, when you passed a
datasetId, or whenrouteBaselineDatesis0. - 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.
| Run | What it can compare against | Verdict you can get | confidence |
|---|---|---|---|
| Day 1 | other departure dates priced in the same run | GOOD_PRICE / TYPICAL / WAIT, basis: route | low to medium |
| Days 2-3 | still the cross-section; your series is filling in | same, basis: route | low to medium |
| Day 4 | your own series takes over, 3 observations | same verdicts, basis: history | low |
| Day 6 | 5 observations | BOOK_NOW becomes reachable | medium |
| Day 11 | 10 observations | full ladder, trend meaningful | high |
| Day 30+ | a month of your own prices | the verdict this product exists for | high |
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
| Event | Price |
|---|---|
| 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.
| Run | Upstream searches | This Actor | Upstream, to you | All-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 answer | 1 | $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. Readbasisbefore acting onverdict. - 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
BASELINErather than dressing them up. - Three prior observations before a
historyverdict, five beforeBOOK_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
bestfares 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.


