Search Google Flights programmatically. Real prices, typed results, no API key.
Project description
swoop
Search Google Flights programmatically. Real prices, typed results, no API key.
from swoop import search
results = search("JFK", "LAX", "2026-06-15")
for option in results.results[:3]:
print(f"${option.price}")
for leg in option.legs:
itinerary = leg.itinerary
airline = ", ".join(itinerary.airline_names) if itinerary else "Unknown"
print(f" {leg.origin} -> {leg.destination} — {airline}")
[!NOTE] Swoop is not affiliated with Google. It calls undocumented RPC endpoints that can change without notice.
Swoop calls Google Flights' internal GetShoppingResults and GetBookingResults RPC endpoints — the same ones the web app uses when you search for flights. Requests use TLS fingerprint impersonation via primp to match a real browser session. Responses are deeply nested lists (matching an internal protobuf schema) decoded into typed Python dataclasses.
Perch uses Swoop in production to monitor booked flights for price drops, saving users an average of $247 per trip.
Install
pip install swoop-flights
# With CLI (adds `swoop` command)
pip install swoop-flights[cli]
CLI
# Search flights
swoop search JFK LAX 2026-06-15
# Nonstop, sorted by price
swoop search JFK LAX 2026-06-15 --nonstop --sort cheapest
# Roundtrip, business class
swoop search JFK LAX 2026-06-15 -r 2026-06-22 --cabin business
# Official multi-city search
swoop search --leg JFK LAX 2026-06-15 --leg LAX SFO 2026-06-18 --leg SFO SEA 2026-06-21
# Bookable fare for a known flight
swoop price JFK LAX --depart 2026-06-15 DL2300
# Show copy/paste price commands for displayed rows
swoop search JFK LAX 2026-06-15 --show-price-commands
# Script-stable pricing via selector
SELECTOR=$(swoop search JFK LAX 2026-06-15 -o json -q | jq -r '.results[0].selector')
swoop price --selector "$SELECTOR"
More CLI examples
# Roundtrip shorthand pricing
swoop price JFK LAX --depart 2026-06-15 DL2300 --return 2026-06-22 DL2301
# Explicit leg pricing (supports 3+ legs)
swoop price --leg JFK LAX 2026-06-15 DL2300 --leg LAX SFO 2026-06-18 UA544 --leg SFO SEA 2026-06-21 AS331
# CSV for spreadsheets
swoop search JFK LAX 2026-06-15 -o csv -q > flights.csv
# Search JSON for piping
swoop search JFK LAX 2026-06-15 -o json -q | jq '.results[0] | {selector, price, legs}'
# Filter by airline and time window
swoop search JFK LAX 2026-06-15 -a DL -a UA --depart-after 8 --depart-before 14
Run swoop search --help for all options.
[!TIP] Search shows shopping totals for browsing. Use
--show-price-commandsfor copy/pasteswoop price --selector ...commands in human output, or useselectorfrom JSON withswoop price --selector ...in scripts.
Python API
One-way search
from swoop import search
results = search("SFO", "JFK", "2026-06-15")
for option in results.results[:3]:
print(f"${option.price}")
for leg in option.legs:
itinerary = leg.itinerary
if itinerary is None:
continue
print(f" {leg.origin} -> {leg.destination}")
print(f" {itinerary.airline_names}, {itinerary.stop_count} stops")
print(f" {itinerary.travel_time} min")
print(results.is_complete)
search() and search_legs() return shopping totals. Use check_price(), price_legs(), or price_selector() when you need the bookable fare for one chosen itinerary.
More examples
Price check for a specific flight
from swoop import check_price
result = check_price("DL2300", origin="JFK", destination="LAX", date="2026-06-15")
if result:
print(f"${result.price}")
result = check_price(
"DL2300", origin="JFK", destination="LAX", date="2026-06-15",
return_flight_number="DL2301", return_date="2026-06-22",
)
if result:
print(f"${result.price} roundtrip — {result.fare_brand}")
for leg in result.resolved_legs:
print(f" {leg.flight_summary} {leg.origin}->{leg.destination} ({leg.selection})")
Price a chosen search result by selector
from swoop import price_selector, search
results = search("JFK", "LAX", "2026-06-15")
option = results.results[0]
price = price_selector(option.selector)
if price:
print(f"${price.price} — {price.fare_brand}")
Leg-based search and pricing
from swoop import SearchLeg, SelectedLeg, price_legs, search_legs
# Search with explicit legs (official entrypoint for multi-city)
results = search_legs([
SearchLeg(date="2026-06-15", from_airport="JFK", to_airport="LAX"),
SearchLeg(date="2026-06-18", from_airport="LAX", to_airport="SFO"),
SearchLeg(date="2026-06-21", from_airport="SFO", to_airport="SEA"),
])
for option in results.results:
print(option.selector, option.price)
for leg in option.legs:
print(f" {leg.origin}->{leg.destination}")
# Price with explicit legs
result = price_legs([
SelectedLeg(flight_number="DL2300", origin="JFK", destination="LAX", date="2026-06-15"),
SelectedLeg(flight_number="UA544", origin="LAX", destination="SFO", date="2026-06-18"),
SelectedLeg(flight_number="AS331", origin="SFO", destination="SEA", date="2026-06-21"),
])
Roundtrip search
results = search("SFO", "JFK", "2026-06-15", return_date="2026-06-22")
for option in results.results:
print(option.price) # roundtrip total
Cabin class and filters
from swoop import search, SORT_CHEAPEST
results = search(
"LAX", "NRT", "2026-06-15",
cabin="business", # economy, premium-economy, business, first
max_stops=0, # nonstop only
sort=SORT_CHEAPEST, # cheapest first
airlines=["NH", "JL"], # filter to specific carriers
earliest_departure=8, # depart after 8am
latest_departure=14, # depart before 2pm
)
Booking details (fare options)
from swoop import search, get_booking_results
results = search("JFK", "LAX", "2026-06-15")
option = results.results[0]
itinerary = option.legs[0].itinerary
# Get fare tiers — just pass the itinerary
options = get_booking_results(itinerary)
for opt in options:
print(f"${opt.price} — {opt.brand_label} ({opt.fare_family})")
[!TIP] Google rate-limits aggressively. All RPC functions default to
retries=2with exponential backoff and jitter. Increase toretries=3for extra resilience.
How it works
Swoop reverse-engineers the FlightsFrontendService RPC interface that powers Google Flights. Search parameters are encoded as nested JSON arrays matching Google's internal protobuf schema, then sent as HTTP POST requests. The HTTP client uses TLS fingerprint impersonation (via primp) so requests are indistinguishable from a real Chrome session.
Responses arrive as deeply nested list structures — no field names, just positional indices. Swoop's decoder walks these structures and maps them to typed Python dataclasses (Itinerary, Segment, Layover, CarbonEmissions, etc.) with named attributes.
graph LR
A["search()"] --> B["RPC request"] --> C["Google Flights"] --> D["nested lists"] --> E["typed dataclasses"]
API reference
search(origin, destination, date, **kwargs)
Search Google Flights and return a SearchResult.
| Parameter | Type | Default | Description |
|---|---|---|---|
origin |
str |
required | Origin IATA code |
destination |
str |
required | Destination IATA code |
date |
str |
required | Departure date (YYYY-MM-DD) |
return_date |
str | None |
None |
Return date for roundtrip |
cabin |
str |
"economy" |
economy, premium-economy, business, first |
adults |
int |
1 |
Number of adults |
children |
int |
0 |
Number of children (2–11) |
infants_in_seat |
int |
0 |
Number of infants in seat |
infants_on_lap |
int |
0 |
Number of infants on lap |
max_stops |
int | None |
None |
None=any, 0=nonstop, 1=1 stop, 2=2 stops |
sort |
int |
SORT_DEPARTURE_TIME |
Sort order constant |
airlines |
list[str] | None |
None |
Filter by airline codes |
flight_number |
str | None |
None |
Filter to a specific flight number; carrier is also added to the first-leg airline filter |
include_basic_economy |
bool |
False |
Include basic economy fares (excluded by default so prices reflect Main Cabin) |
timeout |
int |
90 |
HTTP timeout in seconds |
retries |
int |
2 |
Retries on HTTP 429 with exponential backoff + jitter |
country |
str | None |
None |
Two-letter country code for point of sale (e.g. "GB"). Controls currency and available fares |
proxy |
str | None |
None |
Proxy URL for routing requests |
max_results |
int | None |
None |
Max trip combinations for beam search (multi-leg only) |
beam_width |
int | None |
None |
Candidate prefixes per stage (multi-leg only) |
time_budget |
int | None |
None |
Seconds before beam search stops exploring (multi-leg only) |
Returns SearchResult. Empty results mean no matches were found. Prices in search results are shopping totals.
search_legs(legs, **kwargs)
Search one or more explicit legs and return a trip-level SearchResult. This is the public multi-city entrypoint.
price_legs(legs, **kwargs)
Price one or more explicit legs and return PriceResult | None.
price_selector(selector, **kwargs)
Price a selected trip row by opaque selector and return PriceResult | None.
search_raw(origin, destination, date, **kwargs)
Low-level single-pass search escape hatch. Returns RawSearchResult with raw best and other itinerary buckets from one RPC pass.
check_price(flight_number, *, origin, destination, date, **kwargs)
Look up the current bookable fare for a specific flight. Optimized for the "what does flight X cost?" use case.
| Parameter | Type | Default | Description |
|---|---|---|---|
flight_number |
str |
required | Flight number (e.g. "DL2300") |
origin |
str |
required | Origin IATA code |
destination |
str |
required | Destination IATA code |
date |
str |
required | Departure date (YYYY-MM-DD) |
return_flight_number |
str | None |
None |
Return flight number for roundtrip |
return_date |
str | None |
None |
Return date for roundtrip |
cabin |
str |
"economy" |
Cabin class |
adults |
int |
1 |
Number of adults |
children |
int |
0 |
Number of children (2–11) |
infants_in_seat |
int |
0 |
Number of infants in seat |
infants_on_lap |
int |
0 |
Number of infants on lap |
include_basic_economy |
bool |
False |
Include basic economy fares |
timeout |
int |
90 |
HTTP timeout in seconds |
retries |
int |
2 |
Retries on HTTP 429 |
country |
str | None |
None |
Two-letter country code for point of sale |
proxy |
str | None |
None |
Proxy URL for routing requests |
Returns PriceResult | None. PriceResult has price, fare_brand, is_basic_economy, booking_options, itinerary, resolved_legs, rpc_calls.
get_booking_results(itinerary_or_token, **kwargs)
Get fare options for a specific itinerary. Pass an Itinerary object directly, or a booking token string with explicit origin, destination, date, and selected_legs. Returns list[BookingOption] with price, brand_label, brand_code, fare_family, etc.
set_country(country_code)
Set the default country code for all subsequent requests. Controls point of sale, currency, and available fares. Pass None to clear.
set_proxy(proxy_url)
Set the default proxy URL for all subsequent requests. Pass None to clear.
Result types
PriceResult—price: int,currency: str | None,fare_brand: str | None,is_basic_economy: bool,booking_options: list[BookingOption],itinerary: Itinerary | None,resolved_legs: list[ResolvedLeg],rpc_calls: intResolvedLeg—flight_summary: str,origin: str,destination: str,date: str,itinerary: Itinerary | None,selection: strSelectedLeg—flight_number: str,origin: str,destination: str,date: strSearchLeg—date: str,from_airport: str,to_airport: str,max_stops: int | None,airlines: list[str] | NoneSearchResult—results: list[TripOption],price_range: PriceRange | None,is_complete: bool,currency: str | NoneTripOption—selector: str,price: int | None,currency: str | None,legs: list[TripLeg]TripLeg—origin: str,destination: str,date: str,itinerary: Itinerary | NoneRawSearchResult— low-levelbest: list[Itinerary],other: list[Itinerary],price_range: PriceRange | NoneItinerary— Full itinerary withprice,flights,layovers,travel_time,booking_token,carbon_emissionsSegment— Segment details:airline,flight_number,aircraft,legroom,co2_grams,amenitiesLayover— Stop info:minutes, airports,is_overnightCarbonEmissions—this_flight_grams,typical_for_route_grams,difference_percent
Constants
| Constant | Value | Description |
|---|---|---|
SORT_TOP |
1 |
Google's default ranking |
SORT_CHEAPEST |
2 |
Cheapest first |
SORT_DEPARTURE_TIME |
3 |
By departure time |
SORT_ARRIVAL_TIME |
4 |
By arrival time |
SORT_DURATION |
5 |
Shortest first |
Error handling
All exceptions inherit from SwoopError. Catch SwoopRateLimitError for HTTP 429, SwoopHTTPError for other HTTP failures, and SwoopParseError for response decoding issues.
Contributing
Issues and pull requests welcome at github.com/saraswatayu/swoop.
Testing notes:
- Push and PR CI runs the deterministic offline suite only:
python -m pytest tests/ -v -m 'not live' - Benchmarks are opt-in and stay skipped in normal runs unless you pass
--run-benchmarks(or use--benchmark-only). - Live Google canaries run separately in the
live-canaryworkflow on a weekly schedule or by manual dispatch. - Mutation testing is available by manual dispatch in the
mutationworkflow and is scoped to_selection,_booking,decoder, andrpc. - Real-world bugs should be added to the incident regression bank in
tests/incidents/manifest.jsonwith a linked regression test or sanitized fixture. - When a live canary finds a useful new payload shape, promote it manually into
tests/fixtures/contract_corpus_manifest.jsonand the tracked fixture corpus after review.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swoop_flights-0.4.1.tar.gz.
File metadata
- Download URL: swoop_flights-0.4.1.tar.gz
- Upload date:
- Size: 48.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eda324f9457cef6302fce4db46efba991c9d92e13e66dbd098a47a5d963e0e5e
|
|
| MD5 |
d16c071fab9fed33511efd93cb2e77b5
|
|
| BLAKE2b-256 |
344568284a08a006cf5aa10d8f9c81f27b5804e8f601edc676b432ecb35d1fca
|
Provenance
The following attestation bundles were made for swoop_flights-0.4.1.tar.gz:
Publisher:
ci.yml on saraswatayu/swoop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swoop_flights-0.4.1.tar.gz -
Subject digest:
eda324f9457cef6302fce4db46efba991c9d92e13e66dbd098a47a5d963e0e5e - Sigstore transparency entry: 1208047521
- Sigstore integration time:
-
Permalink:
saraswatayu/swoop@69a8aeaf3e12242295e40d545e7c43d792a78978 -
Branch / Tag:
refs/tags/swoop-v0.4.1 - Owner: https://github.com/saraswatayu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@69a8aeaf3e12242295e40d545e7c43d792a78978 -
Trigger Event:
push
-
Statement type:
File details
Details for the file swoop_flights-0.4.1-py3-none-any.whl.
File metadata
- Download URL: swoop_flights-0.4.1-py3-none-any.whl
- Upload date:
- Size: 55.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c63fd6009c1de130e18f48d7d541ce2d3fb9d73c154bca45334a3dbf75d77468
|
|
| MD5 |
0b0487c1e70f325bd4b6ffc01888075e
|
|
| BLAKE2b-256 |
3c0019a8b91463f2e11527b0edba70a2f4f2648b83850ead21276f6b46e7fae2
|
Provenance
The following attestation bundles were made for swoop_flights-0.4.1-py3-none-any.whl:
Publisher:
ci.yml on saraswatayu/swoop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swoop_flights-0.4.1-py3-none-any.whl -
Subject digest:
c63fd6009c1de130e18f48d7d541ce2d3fb9d73c154bca45334a3dbf75d77468 - Sigstore transparency entry: 1208047560
- Sigstore integration time:
-
Permalink:
saraswatayu/swoop@69a8aeaf3e12242295e40d545e7c43d792a78978 -
Branch / Tag:
refs/tags/swoop-v0.4.1 - Owner: https://github.com/saraswatayu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@69a8aeaf3e12242295e40d545e7c43d792a78978 -
Trigger Event:
push
-
Statement type: