Flight Deals & Mistake Fare Scraper
Pricing
from $4.00 / 1,000 deal founds
Flight Deals & Mistake Fare Scraper
Collect flight deals, flash sales and error fares from four deal publishers into one newest-first table: airline, route, price, trip type and how old the deal is — plus both airport codes, fare class, routing, stopover rules and mileage where the deal spells them out.
Error fares, flash sales and mistake fares are published in public — and then they die, usually within a day or two. This actor collects them from four deal publishers at once — The Flight Deal, Fly4free, Travelfree and CheapOair — and returns one newest-first table: both cities, the fare (both fares, when a deal quotes a basic and a regular price), the airline and the trip type where the deal states them, and how old the deal is, so nothing stale is ever presented as live. The Flight Deal and the on-sale fares also carry both airport codes; the two European publishers write place names in prose and no codes at all, and those rows say so by leaving the code fields empty. Where a deal spells out the small print, you also get the fare class, the full routing, the stopover rules and the mileage it earns, right down to cents per mile. The archive behind it goes back to 2011, so you can either watch today's deals or build a decade of them.
What you can do with it
- Run a deal-alert product. Collect what was published since your last run, filter to the departure cities your audience flies from, and push the fresh ones out.
- Fill an affiliate or content pipeline. Every deal arrives structured — airline, route, price, trip type, sample travel dates, validity window — instead of as a headline you have to read.
- Study fare behaviour over years. Fourteen years of published deals, by route, airline, season and price, is a research corpus you cannot buy off the shelf.
- Spot mistake fares faster. On-sale fares carry the seller's own Green/Red deal rating and the price bands the same route usually sells in, so an outlier stands out.
- Feed a points-and-miles tool. Fare class, routing, miles flown, elite-qualifying and redeemable miles, and cents-per-mile come straight off the deal.
- Track who is discounting what. Which airlines are dumping seats, on which routes, from which cities, and how often.
What you get
One row per deal, newest first. A real example, abridged:
{"dealId": "theflightdeal:322415","source": "theflightdeal","title": "Cathay Pacific: San Francisco - Kota Kinabalu, Malaysia. $843. Roundtrip, including all Taxes","permalink": "https://www.theflightdeal.com/2026/08/15/cathay-pacific-san-francisco-kota-kinabalu-malaysia-843-roundtrip-including-all-taxes/","publishedAt": "2026-08-15T18:33:02Z","ageDays": 0,"isLikelyExpired": false,"dealType": "flight","airlines": ["Cathay Pacific"],"originCity": "San Francisco","originIata": "SFO","destinationCity": "Kota Kinabalu","destinationIata": "BKI","destinationCountry": "Malaysia","priceLowest": 843,"priceBasic": null,"priceRegular": null,"currency": "USD","currencySource": "symbol","tripType": "roundtrip","isBidirectional": null,"fareIncludesTaxes": true,"sampleTravelDates": "October 14th - 21st","fareValidity": "Valid for travel until late October for Monday through Thursday departures and returns. Availability is limited. Must purchase at least 7 days in advance of departure.","fareClass": "Q","routing": "SFO - HKG (Hong Kong) - BKI (Kota Kinabalu) - HKG - SFO","routingLegs": ["SFO", "HKG", "BKI", "HKG", "SFO"],"stopoverRules": "Two permitted at $100","milesFlown": 16100,"eliteQualifyingMiles": 3464,"redeemableMiles": 3464,"centsPerMile": [{ "value": 5.2, "label": null }],"centsPerMileLowest": 5.2,"categories": ["San Francisco"],"alsoSeenIn": [],"dealDetailsIncluded": true,"isEstimated": false,"collectedAt": "2026-08-16T12:00:00.000Z"}
An on-sale fare looks the same, and adds what that seller publishes: dealBucket (Green / Red), priceQuantiles, flightNumbers, departureDate, numberOfStops and priceAdvertised.
Input reference
| Field | Type | Default | What it does |
|---|---|---|---|
sources | array | all four | Which publishers to collect from: theflightdeal, fly4free, travelfree, cheapoair. |
originCities | array | — | Keep only deals leaving from these places: airport codes (JFK), city-wide codes (NYC), or names (San Francisco). Empty means everywhere. |
keyword | string | — | Keep only deals whose text mentions this word — an airline, a country, a city, or a theme such as "business class". |
tripType | string | any | any, oneway or roundtrip. Deals whose trip type was never stated are left out when you pick one. |
currency | string | USD | Currency for the on-sale fares: USD or GBP. Deal write-ups keep whatever currency their author used. |
since | string | — | Only deals published after this date (YYYY-MM-DD). Ideal for scheduled runs that should pick up only what is new. |
maxAgeDays | integer | — | Drop anything older than this many days (0–3,650). Empty keeps every age. |
includeBackfill | boolean | false | Work back through the historical archive as well as the latest deals. |
maxDeals | integer | 200 | Most rows this run will produce, across every source, after filtering and merging (1–25,000). |
includeDealDetails | boolean | true | Read the deal write-up as well as its headline. On by default. It is where the fare class, routing, stopover rules, mileage earning, flight numbers, price bands and both airport codes live, so turning it off leaves archive rows with empty code, routing and mileage fields and no publisher tags — each such row says deal-detail-not-read in dataNotes. |
onlyFlightDeals | boolean | true | Leave out hotel and package posts, which some publishers mix into the same stream. |
requirePrice | boolean | true | Leave out posts with no published fare, such as roundups and announcements. |
excludeLikelyExpired | boolean | false | Leave out deals old enough to have probably died. |
likelyExpiredAfterDays | integer | 2 | How old a deal must be to be marked as probably gone (1–30). |
Output fields
| Field | Type | What it is |
|---|---|---|
dealId / source / permalink | string | A stable identity, which publisher it came from, and a link to the deal itself. |
title / summary | string | The deal as published, and its opening lines. |
publishedAt / ageDays | string / number | When it was published, in UTC, and how many whole days ago that was. |
isLikelyExpired / isMarkedGone | boolean | null | Whether the deal is old enough to have died, and whether the publisher itself marked it dead. null means the age is unknown — never a guessed false. |
airlines / airlineCodes | array | The airlines on the deal, in the order the deal names them. A code-share written as "Air China / China Eastern / Shanghai Airlines" is returned as three. Empty when the publisher never said who flies it, which is most of the time on the two European feeds. |
originCity / originIata / originCityCode / originText | string | null | Where it leaves from: the city, the airport code where one was published, the city-wide code, and the text exactly as written. The two European publishers publish no airport codes at all, so those rows carry the text and leave the codes empty. |
destinationCity / destinationIata / destinationRegion / destinationCountry | string | null | Where it goes. American deals are written "City, State", so the state goes to destinationRegion and the country reads United States with a note on the row saying it came from the state. Where a state name is also a country's — Georgia is the only one — the country is left empty rather than guessed, and the row says which. |
priceLowest | number | null | The cheapest fare the deal quotes. null when no fare was published — never a guess. |
priceBasic / priceRegular | number | null | Both fares when a deal quotes a basic and a regular price. Which is which comes from the label, never from the order they appear in. |
priceAdvertised | number | null | The headline figure a seller prints beside its own total. It sits below the total and is kept separate for exactly that reason. |
currency / currencySource | string | null | The currency the fare was published in, and whether that came from a stated field or from the symbol printed in the text. Never the currency you asked for. Where one deal is priced twice for two markets — "from £348/€439" — the fare and its currency are taken from the same quote, the first one, and the row notes that a second currency was also quoted. |
requestedCurrency | string | null | What was asked of the on-sale list, so you can see when the two differ. |
tripType / isBidirectional / cabin | string / boolean | null | Return or one-way, whether the deal works in both directions, and the cabin where stated. |
fareIncludesTaxes | boolean | null | Whether the published price includes taxes. |
departureDate / returnDate | string | null | Real dates, where the deal is tied to specific ones. |
sampleTravelDates / fareValidity | string | null | The example dates and the validity window as the publisher wrote them. |
fareClass / fareClasses | string / array | The booking class the deal is filed in. |
routing / routingLegs / routingCodes | string / array | The full leg chain, as text and as a list of airports. |
stopoverRules | string | null | Whether a stopover is allowed and what it costs. |
milesFlown / eliteQualifyingMiles / redeemableMiles / qualifyingDollars | number | null | The mileage the trip earns, as the deal states it. |
centsPerMile / centsPerMileLowest | array / number | null | Value per mile, per fare product, and the lowest of them. |
dealBucket | string | null | The seller's own rating of an on-sale fare: Green or Red. |
priceQuantiles | object | null | The price bands the same route usually sells in, as published. |
priceBaseline | object | null | The seller's historical comparison figures, labelled isReferenceNotLiveHistory: true — see the limits below. |
numberOfStops / flightNumbers | number / array | Stops and the real flight numbers, where a deal identifies specific flights. |
dealType / isDeal | string / boolean | flight, hotel, package or other, and whether the post is a fare deal at all. |
categories | array | The departure-city tags the publisher filed the deal under. |
alsoSeenIn | array | Other publishers whose posting of this fare was matched to this row and merged into it. Empty is the normal case — it never names the row's own publisher. |
cacheExpiryMinutes | number | null | How long an on-sale list stays current before the seller refreshes it. |
dealDetailsIncluded | boolean | Whether the extended detail was attached to this row. |
isEstimated | boolean | true only where the row carries the seller's own modelled comparison figures. The fare, the dates and the routing are always as published. |
dataNotes | array | Anything worth knowing about this specific row: a currency read from a symbol rather than a stated field, a deal priced in two currencies, a country read from a US state, or a write-up left unread because the extended detail was off. |
collectedAt | string | When the row was collected. |
Pricing
You pay per result, not per run.
| What you pay for | Price |
|---|---|
| Deal found | $4.00 per 1,000 rows |
| Deal details added | $1.50 per 1,000 rows |
The first is charged for every deal delivered. The second is charged only when a row actually gained the extended detail — the fare class, routing, stopover rules, mileage earning, flight numbers or price bands. A deal whose publisher printed none of that is delivered without it and costs you nothing extra, and turning the detail off leaves only the first charge.
Worked example. A daily alerting run that collects 150 fresh deals, 120 of them with full detail: 150 × $0.0040 = $0.60, plus 120 × $0.0015 = $0.18 — about 78 cents a day, roughly $23 a month. A one-off research backfill of 5,000 archived deals with detail on all of them costs 5,000 × $0.0040 + 5,000 × $0.0015 = $27.50.
Limits & what this actor cannot do
- Deals die fast, and this actor says so rather than hiding it. The publishers themselves warn that a fare more than two days old is usually gone. Every row carries
publishedAt,ageDaysandisLikelyExpired, and the table puts them in front of the price. - It reports what was published to the public. It does not hold seats, does not book, and cannot guarantee a fare is still available — or that it ever loaded for everyone.
- Nothing is invented. A deal with no published price, no airport code or no travel dates comes back with those fields empty. No price is estimated, converted or carried across from another deal.
- On-sale fares come from a list the seller refreshes on a cycle, not from a live search run for you. Each row carries how long that list stays current, and the fare is a snapshot of it.
- The historical comparison figures on on-sale fares are the seller's own reference records, not live price history. They are labelled
isReferenceNotLiveHistory: trueand are years old. Never presentcheapestEveras today's market low. - A seller's advertised headline figure is not the bookable price. It runs materially below that seller's own total; both are returned,
priceLowestis the total, and the difference is flagged on the row. - Currency is whatever the publisher used — dollars, euros, pounds. Nothing is converted, and a
$in free text is recorded as US dollars with a note saying it was read from a symbol. Where one deal is priced twice for two markets — "from £348/€439" — the fare and its currency are both taken from the first quote, never mixed, and the row notes the second currency. - Airport codes come from two of the four publishers. The Flight Deal spells the codes out inside each deal, and the on-sale fares are published as codes; Fly4free and Travelfree write prose only, so no row from those two carries an airport code — "British Airways direct flights from London to San Francisco for £438" arrives with both cities as text and both code fields empty. Nothing is looked up to fill them in.
- Not every deal names an airport at all. Some publishers write a region — "the Baltic countries", "several German cities" — and those rows carry the text as published. Filtering by departure place keeps only the deals that can be shown to leave from there.
- The airline is only there when the deal names one. The Flight Deal and the on-sale list name a carrier on every deal, and Travelfree names one on most; Fly4free rarely does, so many of its rows arrive with an empty airline even where the headline mentions a carrier. Nothing is inferred from the route.
- The same fare from two publishers is not always spotted as the same fare. A repeat is removed when the route wording, price, currency, trip type and publication date all match, which reliably catches the same publisher posting twice. Two publishers writing the same deal in their own words — "UK to Jamaica from £371" against "UK cities to Montego Bay, JAMAICA from £377" — are delivered as two rows, and both are charged for.
alsoSeenInis filled in only where the match was certain, so an empty value means "not matched", not "not published elsewhere". - A fare re-posted on a later date is a new row, not a duplicate. These publishers re-run a recurring deal months apart, and both postings are kept with their own dates and links, because that repetition is the signal a fare-trend study is looking for.
- Some publishers mix hotel and package offers into the same stream. They are labelled and left out by default; switch
onlyFlightDealsoff to keep them. - If you name no departure place, on-sale fares reflect wherever the run is made from, because that is what the seller features. Name your departure cities to control which fares come back.
- A source that refuses to answer is reported as a failed read, never as "no deals found". The run summary counts sources that published nothing separately from sources that could not be read, and a run where every source failed ends as a failed run rather than a green one with an empty table.
- Speed depends on the size of the job and on how quickly the publishers answer; no fixed throughput is promised.
- The publishers' terms govern automated access. You are responsible for using the data lawfully and in line with those terms.
FAQ
Do I need an account with any of these publishers? 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 still bookable?
Treat every row as a snapshot of what was published. Deal fares are the most perishable prices in travel — many last hours. ageDays and isLikelyExpired are there so you never have to guess, and every row links straight to the deal so you can check it.
Why do some deals have two prices?
Because the deal quotes two — a basic-economy fare and a regular-economy one, often hundreds apart. Both are returned in priceBasic and priceRegular, and priceLowest is the cheaper of them. Which label belongs to which number is taken from the text, never from the order.
Do I get one row or two when two publishers post the same fare?
Usually two. A repeat is merged only when both postings describe the route the same way and agree on the price, currency, trip type and date — which is what a second read of one publisher looks like. Two publishers wording the same deal differently, or quoting it a few pounds apart, are not recognised as one fare and arrive as two rows. When a merge does happen the survivor's alsoSeenIn names the other publisher, and it never names its own.
How far back does the archive go?
To 2011, over 68,000 published deals. Turn on includeBackfill and set maxDeals to how many you want; use since to take only what is new.
Can I schedule it?
Yes. A daily or twice-daily run with since set to your last run is the natural way to use it, and is how an alerting product would be built on top.