Skip to main content

gfly

Google Flights for agents. A read-only, JSON-first flight-search CLI an LLM can drive — no API key, no account, no OAuth.

ci release pypi python license Agent CLI Guidelines: Full

gfly.sh · docs · the read-only Google Flights CLI for agents

┌───────────────────────────────────────────────────────────────────────────────┐
│  ✈  G F L Y · D E P A R T U R E S                              READ-ONLY · NO KEY │
├───────────┬──────────────────────────────┬────────────────────┬─────────────────┤
│  GATE     │  DESTINATION                 │  FLIGHT            │  STATUS         │
├───────────┼──────────────────────────────┼────────────────────┼─────────────────┤
│  search   │  JFK → LHR  itineraries      │  gfly search       │  ● ON TIME      │
│  dates    │  cheapest-day price calendar │  gfly dates        │  ● ON TIME      │
│  multi    │  multi-city journeys         │  gfly multi        │  ● ON TIME      │
│  airports │  resolve IATA codes offline  │  gfly airports     │  ● ON TIME      │
│  doctor   │  health + throttle state     │  gfly doctor       │  ● ON TIME      │
│  schema   │  machine-readable contract   │  gfly schema       │  ● ON TIME      │
└───────────┴──────────────────────────────┴────────────────────┴─────────────────┘

gfly demo


Why gfly

Google has had no public flights API since QPX shut down in 2018. The community tools that fill the gap are built for humans (Rich TUIs) or hand agents an MCP server with a self-declared-unstable JSON shape. gfly is engineered for an LLM agent in a loop:

gfly the others
JSON by default (stable, versioned schemaVersion) ⚠️ "experimental"
schema + embedded agent contract (zero external files)
Semantic exit codes for the real failures (BLOCKED, SCHEMA_DRIFT, RATE_LIMITED)
Token-bounded output (--limit, --offset, --select)
Read-only by design — can't book, can't mutate varies
Persistent politeness throttle (survives the fresh-process-per-call model)
Zero auth on the default backend varies

[!NOTE] The default backend rides a reverse-engineered, undocumented endpoint via fast-flights. It is fragile by nature and will break when Google changes its response — that's exactly why gfly surfaces SCHEMA_DRIFT/BLOCKED as structured, actionable errors and ships a swappable SerpApi backend as the reliability escape hatch. See SECURITY.md and Risks.

Try it in 10 seconds (no install, no key)

uvx gfly search JFK LHR --depart 2026-08-15

That's the whole onboarding. No account, no API key — the agent (or you) just runs it.

Install

Method Command
uv (recommended) uv tool install gfly
uvx (zero-install trial) uvx gfly search JFK LHR --depart 2026-08-15
Homebrew brew install rnwolfe/tap/gfly
pipx pipx install gfly
pip pip install gfly

Requires Python ≥ 3.10. Ships fast-flights (google engine) + offline IATA data; the serpapi backend needs no extra dependency.

Quickstart

# one-way, cheapest first — JSON straight into jq
gfly search JFK LHR --depart 2026-08-15 --sort price --json \
  | jq '.itineraries[] | {price, airlines, stops, durationMinutes}'

# round-trip, business, nonstop only
gfly search SFO NRT --depart 2026-09-10 --return 2026-09-24 --cabin business --stops nonstop

# cheapest day to fly across a window (one search per day — keep it small)
gfly dates JFK LHR --depart-range 2026-08-01..2026-08-07

# multi-city
gfly multi --leg JFK:CDG:2026-08-01 --leg CDG:FCO:2026-08-05 --leg FCO:JFK:2026-08-12

# resolve airports offline (don't make the agent guess codes)
gfly airports search "london"

# paginate: page 2
gfly search JFK LHR --depart 2026-08-15 --limit 5 --offset 5 --json

Output discipline: data on stdout, every note/warning/error on stderr — so a pipe stays clean. At a TTY you get readable tables; piped or --json, you get the stable envelope.

Backends

Backend Auth Data Notes
google (default) none reverse-engineered Google Flights free, fragile, rate-limited; --proxy routes around IP blocks
serpapi API key live SerpApi JSON multi is google-only; set the key once (below)
echo "$SERPAPI_KEY" | gfly auth login --backend serpapi --token-stdin   # → OS keyring
gfly --backend serpapi search JFK LHR --depart 2026-08-15

Authenticate (only if you choose serpapi)

gfly follows the gh model. The google backend needs nothing.

gfly auth login  --backend serpapi --token-stdin   # secret via STDIN, never argv
gfly auth status --backend serpapi                 # tests + redacts; non-zero on problems
gfly auth logout --backend serpapi                 # removes LOCAL credential only
  • Storage order: GFLY_SERPAPI_KEY env → OS keyring → 0600 XDG file fallback (a warning prints if perms can't be secured). Secrets are never accepted via flags (they'd leak to ps//proc).
  • Revocation is separate from logout — rotate the key at serpapi.com.
  • Run gfly doctor anytime to check auth, keyring, connectivity, and throttle state.

Rate limits & not getting banned

The google backend is scraped, so the #1 controllable ban vector is request rate. gfly ships a persistent, cross-process politeness throttle (default --min-interval 12s) — because an agent invokes the CLI as a fresh process each call, the throttle state lives on disk, not in memory.

It fails fast rather than hanging: when a request would be too soon, you get a structured error with retryAfterSeconds — not a silent multi-minute sleep (which would deadlock an agent loop).

gfly search JFK LHR --depart 2026-08-15 --wait          # opt INTO blocking until clear
gfly search JFK LHR --depart 2026-08-15 --min-interval 0 # disable (riskier)
gfly --backend serpapi ...                               # the reliability escape hatch

Politeness reduces ban risk; it doesn't eliminate it (datacenter IPs can be CAPTCHA'd regardless) — that's what --proxy and serpapi are for.

GATE STATUS — exit codes

A first-class contract. gfly schema always prints the authoritative table.

┌──────┬──────────────────┬──────┬───────────────────────────────────────────────┐
│ CODE │ NAME             │ CODE │ NAME                                          │
├──────┼──────────────────┼──────┼───────────────────────────────────────────────┤
│  0   │ ok               │  8   │ retryable (transient network)                 │
│  2   │ usage / parse    │ 10   │ config error                                  │
│  3   │ empty results    │ 13   │ input required (--no-input hit a prompt)      │
│  4   │ auth required    │ 20   │ BLOCKED  (CAPTCHA/soft-block; retryAfter)     │
│  5   │ not found        │ 21   │ SCHEMA_DRIFT (upstream parse broke)           │
│  7   │ rate limited     │ 130  │ cancelled (SIGINT)                            │
└──────┴──────────────────┴──────┴───────────────────────────────────────────────┘

Errors are structured on stderr: { "error", "code", "remediation" } (+ retryAfterSeconds on throttle/block), so an agent can back off, switch backend, or report instead of crashing.

For agents

Follows the Agent CLI Guidelines (v0.3, Full) — read-only by default, structured, self-describing, stable exit codes.

gfly agent        # prints the embedded SKILL.md — the full usage contract, in the binary
gfly schema       # command tree + flags + exit codes + live safety/throttle state + env vars

Itinerary fields are an append-only contract: price, currency, airlines[], flightNumbers[], durationMinutes, stops, layovers[]{airport,minutes}, departure, arrival, origin, destination, co2Grams, co2DeltaPct, isBest, bookingToken. Third-party text is fenced as untrusted by default (--no-wrap-untrusted to disable).

Risks & ToS

  • The default backend uses an unofficial, undocumented endpoint. Expect periodic breakage (surfaced as SCHEMA_DRIFT) and rate-limiting/CAPTCHA (surfaced as BLOCKED). Pin the version.
  • Legitimacy boundary: it's for personal, legitimate-scale use — interactive research and modest agent runs. Don't drive it at bulk/commercial scale or to resell data (violates Google's ToS, and gets the IP blocked). gfly self-throttles to reduce load — it never evades controls (no UA spoofing, CAPTCHA solving, or proxy rotation).
  • gfly is read-only — it searches, it cannot book.
  • The serpapi backend is a third-party paid service with its own ToS; it's a fallback, never the sole path. See SECURITY.md for the credential threat model.

Development

uv sync --extra dev
uv run pytest -q          # contract + behavior tests (incl. the schema-snapshot gate)
vhs demo/gfly.tape        # regenerate the demo GIF

See CONTRIBUTING.md and AGENTS.md.

License

Dual-licensed under either of MIT or Apache-2.0 at your option.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gfly-0.2.0.tar.gz (1.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gfly-0.2.0-py3-none-any.whl (38.3 kB view details)

Uploaded Python 3

File details

Details for the file gfly-0.2.0.tar.gz.

File metadata

  • Download URL: gfly-0.2.0.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for gfly-0.2.0.tar.gz
Algorithm Hash digest
SHA256 dd60d815ec6a833556e07fe71f52a149cb752fcf3afbb5b2d23f8b1ea6cf2c5e
MD5 f2c1a9f104b563fc92d34abbc3cac1a1
BLAKE2b-256 4d0e462a954d5d3929b33e9624efa34f7e6653ecf7c0eb7778dd305d40e90b09

See more details on using hashes here.

File details

Details for the file gfly-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: gfly-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 38.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for gfly-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 48d6ed7ca3044ee7445d83664267d20fc3fab35f24651ac2aa22029b24e3f2c8
MD5 d49672998aa967713f8d001c76928713
BLAKE2b-256 d408279dbf24addc653c92a45a4d582b3cc78e87dd7f85466e6cfe2b320809fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page