Backends
gfly normalizes two data sources behind one stable output contract. You pick one with --backend
(or GFLY_BACKEND); the fields, exit codes, and JSON envelope stay identical either way.
google (default) |
serpapi (opt-in) |
|
|---|---|---|
| Auth | none | API key required |
| Source | reverse-engineered Google Flights endpoint via fast-flights |
SerpApi’s live Google Flights JSON over HTTPS |
flightNumbers |
always [] |
populated |
bookingToken |
always null |
populated |
isBest |
always false |
true for best-flights group |
| Round-trip price | round-trip total, outbound legs only | per direction |
multi command |
supported | not supported |
| Throttle | yes — see Rate Limits | exempt |
| Reliability | breaks when Google changes its endpoint | stable paid API |
google (default)
Section titled “google (default)”The default backend requires no credentials. It sends a base64-encoded protobuf query (tfs) to an
undocumented Google Flights endpoint and parses the response via the
fast-flights v3 library.
# No setup needed — just run it.gfly search JFK LHR --depart 2026-08-15Because this rides a reverse-engineered internal API, breakage is an inherent risk:
SCHEMA_DRIFT(exit 21) — the upstream response no longer parses. The library has drifted. Upgradegfly(andfast-flights), switch to--backend serpapi, or file an issue.BLOCKED(exit 20) — Google served a CAPTCHA or soft-block. The circuit breaker opens and returnsretryAfterSeconds. Back off, then retry; or switch to serpapi.RATE_LIMITED(exit 7) — the local politeness throttle (default 12 s minimum interval) fired before a network call was made.
Field caveats
Section titled “Field caveats”The undocumented endpoint does not expose every field the normalized contract defines:
flightNumbersis always[]. The upstream does not return individual flight numbers.bookingTokenis alwaysnull. Deep-link tokens are not available.isBestis alwaysfalse. The endpoint does not distinguish a “best flights” group from other results.- Round-trip itineraries describe the outbound legs only. The
pricefield is the round-trip total (both legs combined), not just the outbound fare.
If your downstream code depends on any of these fields, use --backend serpapi.
Routing around IP blocks
Section titled “Routing around IP blocks”The --proxy flag (or GFLY_PROXY) passes a proxy URL to the underlying fast-flights call.
This helps when your machine’s IP is already flagged (datacenter IPs are often pre-blocked):
gfly --proxy http://user:pass@proxy.example.com:3128 search JFK LHR --depart 2026-08-15serpapi (opt-in)
Section titled “serpapi (opt-in)”SerpApi is a commercial scraping platform that exposes
Google Flights as a clean JSON API. gfly uses only Python’s stdlib urllib — no extra dependency
is installed.
# Store the key once (OS keyring or 0600 file fallback):echo "$SERPAPI_KEY" | gfly auth login --token-stdin
# Or export for a single session:export GFLY_SERPAPI_KEY=your_key_here
# Then run any search command normally:gfly --backend serpapi search JFK LHR --depart 2026-08-15SerpApi provides 250 free searches per month; paid plans scale beyond that. See Authentication for how the key is stored and resolved.
What serpapi adds
Section titled “What serpapi adds”Because SerpApi parses the structured JSON response rather than a raw protobuf:
flightNumbers— each leg’s carrier code and number (e.g.["BA 117"]).bookingToken— an opaque string you can hand back to Google Flights to pre-fill a booking form. gfly is read-only and never follows this link itself.isBest: true— flights in SerpApi’sbest_flightsgroup are marked; the rest haveisBest: false. This lets your code sort or filter by Google’s own “best” heuristic.
Limits
Section titled “Limits”multiis not supported on serpapi. Thegfly multicommand requires--backend google(the default). Passing--backend serpapitomultiexits withUNSUPPORTED(exit 10).- serpapi is a third-party paid service with its own Terms of Service. It is the reliability escape hatch — treat it as such, not as a replacement for the free google backend in all cases.
Switching backends
Section titled “Switching backends”Per-command
Section titled “Per-command”gfly --backend serpapi search JFK LHR --depart 2026-08-15gfly --backend google search JFK LHR --depart 2026-08-15 # explicit defaultPer-session or globally
Section titled “Per-session or globally”export GFLY_BACKEND=serpapigfly search JFK LHR --depart 2026-08-15 # uses serpapi for all commandsHow breakage surfaces
Section titled “How breakage surfaces”Both backends map upstream failures to the same structured error on stderr, with a machine-readable
code and retryAfterSeconds where relevant:
{ "error": "upstream is blocking requests (CAPTCHA/soft-block); cooling down ~120s", "code": "BLOCKED", "remediation": "back off and retry later, switch --backend serpapi, or supply GFLY_ABUSE_COOKIE", "retryAfterSeconds": 120}| Situation | Exit | Code | Backend |
|---|---|---|---|
| CAPTCHA / soft-block from Google | 20 | BLOCKED |
google only |
| Response no longer parses | 21 | SCHEMA_DRIFT |
google only |
| Throttle interval not elapsed | 7 | RATE_LIMITED |
google only |
| SerpApi 429 | 7 | RATE_LIMITED |
serpapi only |
| SerpApi 401 / missing key | 4 | AUTH_REQUIRED |
serpapi only |
multi called with serpapi |
10 | UNSUPPORTED |
serpapi only |
Agents should parse the code field to decide whether to retry, switch backends, or escalate.
See Exit Codes for the full table and For Agents
for retry-loop patterns.
Checking backend health
Section titled “Checking backend health”gfly doctor probes each configured backend and reports reachability. For google it runs a real
(throttle-exempt) search; for serpapi it checks whether a key is present without burning quota:
gfly doctorChoosing a backend
Section titled “Choosing a backend”Use google (the default) for:
- Quick one-off searches with no setup.
- Multi-city itineraries (
gfly multi). - Situations where a CAPTCHA or drift is tolerable (you can catch exit 20/21 and retry).
Use serpapi when:
- You need
flightNumbersorbookingTokenin every record. - You need the
isBestflag to be meaningful. - You are running sustained automation where CAPTCHA risk is unacceptable.
- The google backend has just drifted and a fix isn’t yet released.