Google Flights Price Fetcher - route & date search
Pricing
from $0.20 / 1,000 results
Google Flights Price Fetcher - route & date search
Fetch real Google Flights offers as clean JSON: carrier, times, stops, segments, price. Plain HTTP, no browser needed. Batch several routes in one run. Every run ends with a RUN_SUMMARY row saying exactly what happened: complete, timed out, rate limited, or zero match. Priced per flight row.
Pricing
from $0.20 / 1,000 results
Rating
0.0
(0)
Developer
T2 Solutions
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Google Flights Price Fetcher — route & date search
Fetch real flight offers from Google Flights for an origin → destination, one day or a round trip: carrier, flight times, aircraft, stops, segment detail and the price Google shows — as clean JSON, one row per offer, ready for Sheets, databases or an agent pipeline.
No headless browser. No login. No proxies. One paced plain-HTTP request per search, exactly like a human tab — because that is what works: Google serves the full offer data server-rendered in the results page, and this actor parses it with a fixture-pinned, test-verified layout contract.
Why use this actor
- 🎯 The price Google shows — the number a shopper sees, not an inferred estimate. Plus each offer's segment list (flight numbers, aircraft) when it's a multi-leg trip.
- ⚡ Plain HTTP, no browser spin-up — most rivals run Puppeteer/Playwright; that burns compute and adds flakiness. This one reads the page like the Transport you already have: one GET per search.
- 🧾 It reports its own honesty — every run ends with exactly one
RUN_SUMMARYrow (complete/partial/timed_out/zero_match), so an empty result set never masquerades as a success. - 🧮 Batch input — search several routes in one run with a shared fail-safe deadline.
- 🤖 Agent-ready — deterministic JSON (
offerIdfingerprints are stable across runs),LIMITED_PERMISSIONS-compatible, and an MCP section below: an agent can drive it unattended. - 🛡️ Fail-safe deadline — never hangs past a wall you set; partial results are flushed and honestly labeled instead of vanishing.
Input
| Field | Type | Default | Description |
|---|---|---|---|
jobs | array of objects (required) | — | One or more route jobs (see below). |
failSafeDeadlineMinutes | integer | 55 | Hard wall for the whole run. On expiry running stops, fetched rows are kept, and RUN_SUMMARY records timed_out. A value ≤ 0 fails fast. |
pacingSeconds | integer | 20 | Seconds between search fetches (min 5). Higher is gentler; lower is faster. |
maxOffers | integer | 200 | Flight rows kept per job (Google returns best-first; the cap applies after that order). |
Job object (route type)
| Field | Type | Description |
|---|---|---|
type | string | "route" (default). A "calendar" job is accepted but completes as an explicitly-null calendar entry with a documented reason in RUN_SUMMARY.calendarUnavailable (v1 scope: routes). |
label | string | Your name for the job; shows up on every row. Default job-N. |
origin | string (required) | Origin airport/city code, e.g. JFK. |
destination | string (required) | Destination code, e.g. LAX. |
departureDate | string (required) | YYYY-MM-DD. |
returnDate | string (optional) | Return date for round trips (on X through Y). Omit for one-way. |
cabin | string (optional) | ECONOMY (default), PREMIUM_ECONOMY, BUSINESS, FIRST. |
passengers | integer (optional) | 1–9; appended as N adults when > 1. |
maxOffers | integer (optional) | Per-job cap, overriding the global maxOffers for this job only. |
Example input — three routes in one run:
{"jobs": [{ "type": "route", "label": "jfk-lax-rt", "origin": "JFK", "destination": "LAX","departureDate": "2026-11-07", "returnDate": "2026-11-14" },{ "type": "route", "label": "sfo-nrt-jan", "origin": "SFO", "destination": "NRT","departureDate": "2027-01-10", "returnDate": "2027-01-20", "cabin": "BUSINESS" },{ "type": "route", "label": "mia-gru-oneway", "origin": "MIA", "destination": "GRU","departureDate": "2026-11-21" }],"failSafeDeadlineMinutes": 55}
Output
One row per flight offer (columns per the table), then exactly one final RUN_SUMMARY row.
| Column | Nullable | Notes |
|---|---|---|
recordType | no | Absent on offer rows; "RUN_SUMMARY" on the final row. |
offerId | no | Stable fingerprint (route + times + carrier + price + flight number) — identical queries across runs give identical IDs, so agents can diff price changes. |
jobLabel | no | The job's label. |
origin / destination | no | IATA codes as parsed from the offer. |
departureAt / arrivalAt | no | Local (airport-local) time, ISO-like YYYY-MM-DDTHH:MM:SS. |
durationMinutes | yes | Computed from the offered times (minutes). |
stops | yes | 0 = nonstop. |
segments | yes | For multi-leg trips: per-segment airports, times, dates, flight number, aircraft. null when not published. |
carrier | yes | Display name (e.g. "Delta"), else the code. |
priceUsd | no (skipped rows counted) | The price Google shows. |
pricePerPassengerUsd | always null | No per-passenger split is published; we don't invent numbers. |
bookingUrl | always null | Google's booking blob is opaque, not a URL; we don't fabricate links. |
fareBrand | always null | Not exposed by the results page payload. |
fetchedAt | no | UTC timestamp of the fetch. |
RUN_SUMMARY (the honesty row)
| Field | Meaning |
|---|---|
state | complete · partial (some jobs failed or input was dropped) · timed_out (deadline wall) · zero_match (every job fetched fine but Google returned no offers) |
rowsRequested / rowsFetched | Capped demand vs what was delivered. |
jobsTotal / jobsOk / jobsFailed | Per-job counts. |
partialReason | deadline · rate_limited · parse_failure · job_cap · row_cap · input_invalid · … |
calendarUnavailable | Calendar-type jobs with the documented v1 reason (explicitly-null calendar, never an error loop). |
jobResults | Per-job outcome: rows, skipped/unparsed/duplicate offer counts. |
Zero-match example (a real empty run — an agent should read this, not panic):
{"recordType": "RUN_SUMMARY","state": "zero_match","rowsRequested": 200,"rowsFetched": 0,"jobsTotal": 1,"jobsOk": 1,"jobsFailed": 0,"partialReason": null,"calendarUnavailable": null,"jobResults": [ { "label": "jfk-lax", "status": "ok", "rows": 0 } ]}
Charging matches this row: you pay per flight row (result event); a zero-match run charges zero results.
Use with AI agents (MCP)
Every Apify actor is callable by AI agents — this one is built to behave when they drive it:
-
Apify MCP server — add
https://mcp.apify.comas an MCP server in your agent (Claude, Cursor, etc.), authenticate with an Apify API token, and this actor appears as a tool whose input is the plain JSON schema above. -
REST — one call starts a run, one call reads the results:
curl -X POST "https://api.apify.com/v2/acts/YOUR~google-flights-price-fetcher/runs?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"jobs":[{"origin":"JFK","destination":"LAX","departureDate":"2026-11-07"}]}'# results: GET /v2/acts/YOUR~google-flights-price-fetcher/runs/{runId}/dataset/items?token=YOUR_TOKEN -
Safe to hand over — the actor runs under Apify's
LIMITED_PERMISSIONS: a run can only do what a run needs (its own dataset and key-value store, plus the Google Flights page it fetches), nothing else in your account. -
It reports its own honesty — the
RUN_SUMMARYrow means an agent can tell a complete scan from a timed-out, rate-limited, invalid-input or zero-match one without guessing from an empty dataset. -
Batch input —
"jobs": [ {...}, {...} ]resolves several routes in one call, which is how agents usually want to use it.
Migrating from another Google Flights scraper?
An honest comparison — including where another tool may still be right. (Figures are what the respective Store pages showed in our late-October 2026 recon; verify live stats yourself.)
| If you're on | What changes |
|---|---|
| kaix/google-flights-scraper (volume leader) | Cheaper per flight ($0.00006) and hugely popular — but no rating and no review presence. This actor is for agents who need the RUN_SUMMARY honesty contract and stable offerIds more than the absolute floor price. |
| johnvc/Google-Flights-Data-Scraper | Priced per page ($0.03/page); this one is per flight row, and you can cap rows per job. |
| memo23/google-flights-scraper | memo23 is the field's README/quality bar; this actor answers with plain-HTTP speed (no browser compute), per-row pricing, and the agent-facing honesty row. |
| lergassy/google-flights-scraper | Strongest rated (4.86★/11); a fair pick if breadth of output fields matters more than pacing control and batch deadlines. |
| automation-lab/google-flights-scraper | ~40% of runs were failing in our snapshot — the actor's core problem. Every run here ends in a state that says what actually happened. |
Not the right tool if: you need fare-class breakdowns, seat maps, or per-passenger price splits — Google's results-page payload doesn't publish them and we won't invent them.
Pricing (proposed — pending publish sign-off)
Pay-per-event: you pay per flight row delivered, and a zero-match run charges zero results.
| Price per flight row | |
|---|---|
| This actor (proposed) | $0.0002 |
| Field floor (kaix) | $0.00006 |
| Field median | ~$0.002 |
| Per-page rivals | $0.03/page |
Why $0.0002: at ~1–30 offers per typical route query, a single-route run lands around $0.0004 – $0.006 — below the field median, well above the floor, and comfortably positive after 80% revenue share and platform usage (~25 s of 1-GB compute per search at one request per ~20 s pacing ≈ $0.00003 usage per search, so every event keeps >8× its usage). Final number is decided at publish time (see the control-plane MONEY.md margin check).
FAQ
How fresh are the prices? As fresh as a search: one paced GET per job at run time. Prices are Google's own snapshot from your run's moment, not a cached feed.
Does this hit rate limits? Pacing defaults to 20 s between searches with 30/120/300 s backoff on a rate limit — the same posture Google demonstrably tolerates for normal users (verified with a 20-request sustained probe over 8 minutes: 100% clean). A rate-limited job is retried, then recorded as partialReason: rate_limited, never silently dropped.
Round trips? Yes — set returnDate. The search covers the pair in one query, exactly as the site does.
Can I get the price calendar (cheapest by date)? Not yet. Calendar-type jobs are accepted and complete with an explicitly-null calendar plus a documented reason in RUN_SUMMARY.calendarUnavailable — it's the honest "not built yet", not an error.
Can I localize? The query pins hl=en&curr=USD (defaults); cabin and passengers are honored. Non-USD/localized output is on the roadmap.
Something failed mid-run. Read RUN_SUMMARY: jobsFailed and per-entry reason tell you which route and why. Partial runs keep the rows that succeeded.
Development (this repo)
python test_fetcher.py # 39 tests — fixture-pinned layout contract + transportpython test_main_wiring.py # 26 tests — run orchestration with a fake SDKpython -m main # local run with INPUT.json in cwd
Design/parity docs: _bmad-output/architecture.md (endpoint choice, failure-mode table, charging, caps), _bmad-output/fixture-flights-jfk-lax.html (golden page the parse paths are pinned to), spec in _bmad-output/specs/spec-google-flights-scraper/.