gflights
Unofficial async Rust client for the Google Flights web API.
Search flights, compare prices across a date range, retrieve booking offers, and resolve booking URLs — all without an official API key.
Disclaimer: This library talks to the same endpoints used by the Google Flights website. It is not affiliated with or endorsed by Google. Usage is subject to Google's Terms of Service.
Features
- Flight search — one-way, return, multi-stop itineraries
- Price graph — cheapest fares across a configurable date range
- Date grid — full departure × return price matrix for round trips
- Booking offers — airline/OTA offers with prices and booking URLs
- Flight deals — discounted destinations from an origin (price vs typical, discount %, booking link)
- City / airport lookup — resolve city names and IATA codes
- Multi-airport search — up to 7 departure or destination airports
- Airline / alliance filters — include or exclude specific airlines or alliances (oneworld, SkyTeam, Star Alliance)
- Connection filters — require layover through specific airports; set min/max layover duration
- Lower-emissions filter — restrict to flights with below-average CO₂
- Locale support —
language+countryfor non-English results - Sort order — Best · Price · Duration · Departure time · Arrival time
- CO2 / emissions — included in parsed itinerary data
- Layover details — connection time, airport codes, overnight warnings
- Rate limiting — built-in governor-based token-bucket limiter
- Retry logic — exponential back-off for transient 5xx / timeout errors
- CLI — interactive REPL and one-shot subcommands (
search,graph,dgrid,offer)
Installation
[]
= "0.1.0"
= { = "1", = ["full"] }
CLI
The crate ships a gflights binary. Install it with:
One-shot mode
# Search flights
# Round trip with filters
# Multi-city (open-jaw) search
# Price graph (cheapest fare per day over 3 months)
# Departure × return price grid
# Booking offers with clickable URLs (OSC 8, supported in most modern terminals)
# Find discounted destinations (Google Flights deals)
# Explore cheap destinations (Google Flights Explore)
# Find cheapest departure dates (one-way)
# Find cheapest round-trip combinations (fixed trip length)
# Search with emissions column and layover detail
Interactive REPL
Run gflights with no arguments to enter an interactive session with history:
gflights> search --from LHR --to JFK --date 2026-08-01
gflights> graph --from MXP --to SYD --date 2026-09-01 --months 2
gflights> dgrid --from LHR --to JFK --dep-start 2026-08-01 --dep-end 2026-08-07 --ret-start 2026-08-15 --ret-end 2026-08-22
gflights> quit
search flag reference
| Flag | Default | Description |
|---|---|---|
--from <CODE> |
required | Departure airport IATA code or city name |
--to <CODE> |
required | Destination airport IATA code or city name |
--date <YYYY-MM-DD> |
required | Outbound departure date |
--return <YYYY-MM-DD> |
one-way | Return date |
--adults <N> |
1 |
Number of adult passengers |
--class <CLASS> |
economy |
economy · premium-economy · business · first |
--stops <STOPS> |
all |
all · non-stop · one-stop |
--sort <SORT> |
best |
best · price · duration · departure-time · arrival-time ¹ |
--airline <CODE> |
— | Include airline IATA code or alliance (ONEWORLD, SKYTEAM, STAR_ALLIANCE). Repeatable. |
--exclude-airline <CODE> |
— | Exclude airline or alliance. Repeatable. |
--via <IATA> |
— | Require connection through this airport. Repeatable. |
--min-layover <MINS> |
none | Minimum layover in minutes (rounded up to 30 min intervals) |
--max-layover <MINS> |
none | Maximum layover in minutes |
--lower-emissions |
off | Restrict to below-average CO₂ flights |
--show-co2 |
off | Add a CO₂ kg column to the table output |
--detail |
off | Show layover airports (via ZRH (65 min)) and +1 for next-day arrivals |
--currency <CURRENCY> |
euro |
Result currency (e.g. us-dollar, british-pound) |
--lang <CODE> |
en |
BCP-47 language subtag |
--country <CODE> |
GB |
ISO 3166-1 alpha-2 country code |
--format <FORMAT> |
table |
table · json |
¹ departure-time and arrival-time are sorted client-side after Google returns results.
dgrid flag reference
| Flag | Default | Description |
|---|---|---|
--from <CODE> |
required | Departure airport IATA code or city name |
--to <CODE> |
required | Destination airport IATA code or city name |
--dep-start <DATE> |
required | First outbound departure date |
--dep-end <DATE> |
required | Last outbound departure date |
--ret-start <DATE> |
required | First return date |
--ret-end <DATE> |
required | Last return date |
--adults <N> |
1 |
Number of adult passengers |
--class <CLASS> |
economy |
Travel class |
--stops <STOPS> |
all |
Stop filter |
--currency <CURRENCY> |
euro |
Result currency |
--format <FORMAT> |
table |
table · json |
Global flags (any subcommand)
| Flag | Default | Description |
|---|---|---|
--proxy <URL> |
none | Route all requests through a proxy. Supports http://, https://, socks5:// (e.g. socks5://127.0.0.1:9050). |
--user-agent <UA> |
random | Override the User-Agent. By default a real desktop browser string is chosen from a rotating pool per run. |
# Search through a local SOCKS5 proxy
Proxy & Docker sidecar
Every request — including the one-time frontend-version probe — is routed through the configured proxy:
let client = new_with_proxy.await?;
=
For rotating egress IPs, run a proxy as a sidecar container and share its
network namespace. The included docker-compose.yml shows
the pattern — gflights runs with network_mode: "service:proxy", so all of
its traffic leaves through the proxy container:
Swap the proxy service for any HTTP/SOCKS5 proxy (or a rotating-IP service)
without touching the gflights service.
MCP server
Run gflights as a Model Context Protocol server over stdio, exposing flight tools to MCP clients such as Claude Desktop:
It speaks JSON-RPC 2.0 on stdin/stdout and exposes these tools: search,
price_graph, cheapest_dates, explore, deals. Each maps its JSON arguments
to the same library calls the CLI uses and returns the result as JSON.
Example client configuration (Claude Desktop claude_desktop_config.json):
The global --proxy and --user-agent flags work here too, e.g.
"args": ["--proxy", "socks5://127.0.0.1:9050", "mcp"].
Quick start
Search for flights
use ;
use ;
async
Run the full worked example:
Price graph across a date range
use ;
use ;
async
Run with:
Multi-city (open-jaw) search
use ;
use NaiveDate;
async
Run with:
Python bindings
The gflights-py/ directory provides async Python bindings built with pyo3 and maturin.
Install
# or build from source:
&&
Quick start
Route arguments are origin / destination (each takes an IATA code or a
city name). Passenger counts are grouped into a Passengers object and the
shared result filters into a SearchFilters object — pass only what you need.
=
# One-way search
= await
# Two adults + a child, non-stop only, sorted by price
= await
# Price graph — cheapest fare per day over 3 months
= await
=
# Departure × return price grid
= await
=
# Cheapest departure dates (one-way)
= await
# Cheapest round-trip combinations (7-night stay)
= await
# Explore cheap destinations
= await
# Run multiple searches concurrently
, = await
Error handling
All API errors raise gflights.GFlightsError (a subclass of Exception).
Input validation errors (bad date, unknown currency, etc.) raise ValueError.
= await
Rate limiting
The client.rate_limited flag is set to True when Google returns HTTP 429.
Call client.reset_rate_limit() after a cooling-off period.
Python type stubs
Full .pyi stubs are shipped with the package. Every method and class is typed and documented, so IDE auto-completion and mypy work out of the box.
Configuration reference
| Builder method | Type | Default | Description |
|---|---|---|---|
.departure(iata, &client) |
async &str |
required | Departure airport / city |
.destination(iata, &client) |
async &str |
required | Destination airport / city |
.departure_location(loc) |
Location |
— | Set departure from existing Location (no network) |
.destination_location(loc) |
Location |
— | Set destination from existing Location |
.add_departure(iata, &client) |
async &str |
— | Add extra departure (max 4) |
.add_destination(iata, &client) |
async &str |
— | Add extra destination (max 4) |
.departing_date(date) |
NaiveDate |
required | Outbound departure date |
.return_date(date) |
NaiveDate |
one-way | Return date (omit for one-way) |
.travelers(t) |
Travelers |
1 adult | Passenger counts |
.travel_class(c) |
TravelClass |
Economy | Economy / Business / First |
.stop_options(s) |
StopOptions |
Any | Non-stop · Max1 · Any |
.sort_order(s) |
SortOrder |
Best | Best · Price · Duration · DepartureTime · ArrivalTime |
.currency(c) |
Currency |
EUR | Result currency |
.language(s) |
&str |
"en" |
BCP-47 language subtag |
.country(s) |
&str |
"GB" |
ISO 3166-1 alpha-2 country code |
.stopover_min(d) |
StopoverDuration |
Unlimited | Minimum layover duration |
.stopover_max(d) |
StopoverDuration |
Unlimited | Maximum layover duration |
.duration_max(d) |
TotalDuration |
Unlimited | Maximum total trip duration |
.departing_times(t) |
FlightTimes |
Any | Outbound departure time window |
.return_times(t) |
FlightTimes |
Any | Return departure time window |
.airlines_include(v) |
Vec<AirlineFilter> |
none | Restrict to these airlines / alliances |
.add_airline_include(f) |
AirlineFilter |
— | Add one airline / alliance to include filter |
.airlines_exclude(v) |
Vec<AirlineFilter> |
none | Exclude these airlines / alliances |
.add_airline_exclude(f) |
AirlineFilter |
— | Add one airline / alliance to exclude filter |
.connecting_airports(v) |
Vec<String> |
none | Require connection through these IATA airport codes |
.add_connecting_airport(s) |
&str |
— | Add one connecting airport |
.lower_emissions(b) |
bool |
false |
Restrict to below-average CO₂ flights |
Travelers
use Travelers;
// [adults, children, infants_in_seat, infants_on_lap]
let travelers = new?; // 2 adults + 1 child
Rules: at least 1 adult, total ≤ 9 passengers.
Airline & connection filters
use ;
let config = builder
.departure.await?
.destination.await?
.departing_date
// Only show British Airways and oneworld alliance members
.add_airline_include
.add_airline_include
// Must connect through Dublin
.add_connecting_airport
// At least 45 min layover, at most 3 hours
.stopover_min
.stopover_max
// Lower CO₂ only
.lower_emissions
.build?;
Rate limiting
ApiClient uses a governor token-bucket rate limiter (default: 10 req/s).
use ApiClient;
use Quota;
use NonZeroU32;
// Custom: 2 requests per second
let quota = per_second;
let client = new_with_ratelimit.await;
If Google returns HTTP 429, the client sets an internal flag and all subsequent requests immediately return RateLimitedError without touching the network. Reset it after a cooling-off period:
if client.is_rate_limited
Retry logic
Transient server errors (HTTP 500/502/503/504) and connection timeouts are automatically retried with exponential back-off. Defaults: 3 attempts, 500 ms base delay, 30 s cap.
use ;
let client = new.await
.with_retry_config;
Set max_attempts: 1 to disable retries entirely.
Error handling
All public async methods return anyhow::Result<T>. Downcast RateLimitedError to check for 429:
use RateLimitedError;
match client.request_flights.await
Known limitations
x-goog-batchexecute-bgr header — computed deep in Google's obfuscated JS from the current time and request payload length — is omitted. Responses are still valid but may occasionally be less accurate (e.g. missing low-fare calendar data). Contributions to reverse-engineer the algorithm are welcome.
Development
# Build
# Unit tests (no network)
# Binary (CLI) tests
# Doc tests
# Live integration tests (requires internet, skipped in CI)
RUN_LIVE_TESTS=1
# Docs
# Lint
# Format
# Security audit
Contributing
Install the pre-commit hook once per clone:
The hook runs cargo fmt, cargo clippy, rustdoc, cargo test --lib, and offline pytest
before every commit. Requires maturin develop to have been run at least once for the Python check.