There is no affordable, self-serve way for a startup to programmatically access flight pricing data.

I needed a solution for Perch, a service that watches flight prices after you book and rebooks you when prices drop. The core loop is simple: search for the same flights you already booked, compare the current price to what you paid, and rebook you if it’s cheaper. To do that at any useful scale, I needed to query flight prices programmatically. Thousands of times a day, across thousands of routes.

Every entity that controls this data has financial incentives to keep it locked up. So I built swoop, a Python library that programmatically accesses Google Flights’ internal endpoints, unlocking flight pricing data for startups at a scale previously available only to well-funded incumbents. What followed was reverse-engineering an API that was never meant to be used: hand-mapping 744 lines of schema from unlabeled nested arrays, building a framework to decode binary tokens field by field, and debugging failures that looked exactly like success.

Who Controls Flight Data?

Three companies control 65% of the global flight distribution market: Amadeus, Sabre, and Travelport. They are called Global Distribution Systems (GDS), and they are the pipes through which most flight bookings flow. They were built on EDIFACT, a messaging protocol formalized in 1987.

~25%Amadeus
+
~22%Sabre
+
~18%Travelport

= 65% of global flight distributionBuilt on EDIFACT (1987). Still running.

Jeremy Wertheimer, co-founder of ITA Software (now Google Flights), described GDS systems as “among the greatest software achievements of their era.” The problem is that airlines are still running that same code 40+ years later.

Full GDS access requires ARC accreditation: a $2,300 application fee, a $20,000 minimum surety bond, and approximately 90 days of processing. Then, there are volume commitments: minimum annual booking segments, with financial penalties if you miss them. Some GDS providers offer self-service developer tiers, but these exclude major airlines and cap what you can do with the data. An ITA Software co-founder put it bluntly: accessing comprehensive flight data outside of small booking volumes costs “millions of dollars per year.”

The per-transaction costs are steep even if you get in. GDS fees range from $3–15 per segment, averaging about $16 per ticket, and can account for up to 25% of the ticket price on low-cost routes.

Airlines don’t just tolerate this system; many are actively making it harder for third parties. They earned approximately $150 billion in ancillary revenue in 2024 (United: $10.6B, Delta: $10.2B, American: $9.2B), and that revenue depends on controlling the end-to-end shopping experience. In 2023, American Airlines entirely removed 40% of its fares from traditional GDS channels. The replacement system couldn’t handle refunds for multi-passenger bookings or support multi-city itineraries. They reversed course months later.

The result is a startup graveyard. 96% of travel companies fail within 5 years. Hipmunk (beloved by users, co-founded by Reddit’s Steve Huffman, acquired by SAP Concur for $58M) shut down in 2020. Its founder built another flight search tool, which also failed. His final word: “I’m not expecting that I’m going to start another travel company, ever.” He left the industry for biology. Adioso, a YC W09 company, worked on flexible flight search from 2008 to 2013, then gave up. The co-founder: “It was too hard to offer a service that customers could really love and trust.”

The companies that survive this landscape have one thing in common: resources that aren’t available to a startup validating an idea. Hopper ingests 300 billion prices per month from 2,000+ sources and has an archive of 7 trillion historic prices, accumulated over a decade. Navan raised $1.3B. Orbitz was literally created by five airlines: American, Continental, Delta, Northwest, and United, specifically to bypass GDS fees.

Every Path I Tried

Amadeus and Sabre. Amadeus has a self-service developer API, but it excludes American Airlines, Delta, and British Airways. It has since become a fully dead end: Amadeus announced it is shutting down the self-service portal entirely, with API key deactivation on July 17, 2026. Sabre is similar. Enterprise sales cycles, accreditation requirements, volume commitments. Not designed for a company that doesn’t yet know if its idea works.

Duffel. Duffel was the most promising option. It’s a modern REST API. $3 per confirmed order, no upfront fees, 300+ airlines. The developer experience was excellent; you can get a search running in under 10 minutes.

Two problems killed it. First, Delta (the airline I travel with most) isn’t supported. Delta hasn’t launched its NDC API at scale, and Duffel’s GDS coverage through Travelport doesn’t include it. Second, support. When I hit integration issues, I never got a response. A developer on Product Hunt described writing 3,500+ lines of integration code before Duffel “stopped replying to emails.” For a product where a missed price drop means a missed rebooking window, that’s untenable.

Scraping airlines directly. The most direct path: go to the source. Every airline has a website. Every website returns prices. Just automate the browser.

This is where I invested months before realizing it wouldn’t work.

Airlines see over 90% of their web traffic from scraping bots, according to Netacea. They have invested accordingly. The defense stack is layered: TLS fingerprinting rejects non-browser clients before any HTTP content is exchanged; behavioral analysis detects automation from mouse movements and typing cadence; JavaScript challenges verify real browser capabilities; and IP reputation systems block datacenter IPs instantly. Residential proxies cost $12–15/GB. One team that successfully scraped 300K prices per day from Google Flights used 50 Lambda functions with rotating residential proxies; community members estimated their proxy costs alone at $500/month.

Even if you get past the defenses, speed kills. A single Playwright-based flight search takes 3–10 seconds. With AI computer use tools, that balloons to 30–120 seconds per query, with a 50–87% success rate. At the query volume Perch needs, browser automation is simply too slow, too expensive, and too fragile.

The legal exposure doesn’t help either. Ryanair won a CFAA case against Booking.com. American Airlines won a $9.4M verdict against Skiplagged. Southwest got a permanent injunction against Kiwi.com.

The endpoint that wasn’t meant for me. Google paid $700 million for ITA Software in 2010, acquiring the most powerful fare search engine ever built. The DOJ forced Google to license it to competitors for five years. Once that obligation expired, Google shut down QPX Express, the only affordable public API for airfare data, in April 2018.

But Google Flights itself still works. It aggregates pricing data from virtually every airline. The search quality is best-in-class. And under the hood, the web app makes RPC calls to internal endpoints. Those endpoints aren’t documented, aren’t public, and aren’t intended for third-party use. But they exist, and they return structured data.

This became the path.

GDS (Amadeus, Sabre, Travelport)$20K bond, ARC accreditation, “millions of dollars per year”
DuffelNo Delta, support stopped responding
Scrape airlines directlyBot detection, $9.4M lawsuit precedent, 30–120s per query
Google QPX Express APIShut down April 2018

Google Flights internal RPCUndocumented. Unintended. Returns structured data.
THE PATH

How It Works

What’s happening under the hood is unusual, and it’s worth understanding why before the details.

Most web APIs return JSON. Google Flights doesn’t. Its frontend communicates with the backend through RPC, and the responses use Protocol Buffers, Google’s binary serialization format. Protobuf is compact and fast, but it requires a schema to interpret. Google doesn’t publish the schema. Without it, the responses are deeply nested arrays, where position is the only clue to meaning. No field names. No documentation.

Reverse-engineering Google Flights isn’t “call the API and read the JSON.” It’s archaeology.

What the Browser Does

When you search for flights on Google Flights, the frontend calls the FlightsFrontendService via RPC. The two endpoints that matter are GetShoppingResults (search) and GetBookingResults (pricing). Requests are application/x-www-form-urlencoded, with a parameter called f.req containing the search criteria as a nested list.

The request encoding is unusual. Filters are serialized to JSON, the JSON string is wrapped in another JSON array, and the result is URL-encoded. JSON inside JSON inside URL encoding. You would never arrive at this by guessing. I found it by watching what the browser does in DevTools and reproducing it exactly.

Responses come prefixed with )]}', a security measure to prevent JSON hijacking, followed by those deeply nested arrays I described above. Thirty-plus top-level elements. Some sub-arrays with 25+ fields. No field names, no documentation, no schema. Just arrays of arrays of arrays, where the meaning of index 13 depends on which parent array you’re inside.

One more obstacle: Google’s servers inspect the initial handshake when a client connects and can tell the difference between a real browser and a Python script. swoop uses primp, an HTTP client that mimics Chrome’s handshake exactly, so Google treats the request as if it came from a real browser.

33 fields per segment. No field names. Position is the only clue.
[3]departure airport”JFK”
[8]departure time(14, 30)or just(14,)or(None, 20)
[12]amenity flags12-element array, changes by cabin class
[13]seat quality1–9 scale (budget economy → premium suite)
[17]aircraft”Boeing 737-800”
[31]CO₂ gramsburied at the end

[1][0][1]display price347 — always major currency unit

Decoding the Response

I reverse-engineered the schema by capturing real responses across multiple routes (JFK to LAX, JFK to LHR, SFO to NRT) and across cabin classes. Economy returns different amenity data than business on the same aircraft. The only way to discover this is to compare.

I discovered every field by changing one variable and diffing the response. Change the cabin class from economy to business, compare the two responses, and whatever changed is cabin-dependent. Repeat for route, date, number of passengers, and airline. The result is 744 lines of documented field mappings, a handmade schema for an API that was never meant to have one.

Each flight segment alone has 33 indexed fields. Departure airport at [3]. Arrival airport at [5]. Aircraft type at [17]. Departure time at [8], which appears sometimes as a tuple (hour, minute), sometimes just (hour,), sometimes with None standing in for midnight. A 12-element amenity flags array at [12], whose values change depending on cabin class. Seat quality on a scale from 1 to 9 at [13], where 1 is budget economy, and 9 is premium suite. Carbon emissions buried at [31]. Codeshare information (operator airline versus ticketing airline) is spread across [2] and [15].

I hand-rolled varint protobuf parsing rather than using generated code, because generated code breaks when the schema changes. This schema changes without notice.

Decoding the Binary

The booking options response was harder. Unlike the shopping results, which are nested JSON arrays, booking options include raw protobuf-encoded tokens: base64-encoded binary data with no structural clues. I built a hypothesis-testing framework to systematically decode them, generating 124 candidate field mappings and scoring each against a corpus of real responses. Twelve survived to production. Roughly 90% of the binary structure remains opaque.

The promoted fields enable Perch’s price verification. When the system checks a price, it doesn’t just trust that the cheapest result is the right flight. It decodes the context tokens and cross-checks the carrier, flight number, origin, destination, and departure time against what it expected. A mismatch means the match is incorrect, regardless of the price.

What Looks Right but Isn’t

Each of these took hours to find and seconds to fix. The pattern is always the same: everything appears to work, the data looks plausible, and the bug hides in the gap between plausible and correct.

Airport nesting depth. Airport codes must be nested exactly three levels deep: [[[code, 0]]]. Four levels return zero results. No error. No warning. The response comes back in exactly the same format as a successful search: correct structure, correct headers, zero flights. I spent an entire evening convinced Google’s API was down before I diffed a working request against a broken one and found the extra bracket.

Price path. Price data lives at index [1] in the itinerary summary, not [1][1]. The wrong path means every price returns $0. The code runs fine, the data looks right, the prices are just silently wrong. This is the kind of bug where you don’t know you have it until a user asks why every flight to Tokyo is free.

Time encoding. Google’s departure time format is inconsistent in three ways: sometimes [8] (hour only), sometimes [8, 10] (hour and minutes), and sometimes (None, 20), where None means midnight. If your code treats None as “missing data” rather than “hour zero,” every red-eye departure shows a question mark instead of a time.

The index swap. Google silently swapped indices [5] and [6] in flight segments; the IATA code and airport full name switched positions. The consequence wasn’t a crash. The code read the airport name field where the code used to be and sent "Los Angeles International Airport" instead of "LAX" in round-trip expansion payloads. Google accepted the request, returned a valid-looking response with zero results, and swoop reported that no return flights were available. I only caught it by diffing a working request captured days earlier against the new one.

Round-trip pricing semantics. When you search for round-trips, Google returns the round-trip total on each result, not the individual leg price. If you sum the outbound price and return price, you double-count. This is the most common mistake in implementations I’ve seen, and it’s an easy one to ship; doubled prices still look like plausible airfares.

The Cabin Class Problem

This was the longest debugging arc in the project: three days, and the kind of problem where each fix reveals a deeper issue.

Perch needs to know what cabin class a fare belongs to. When a user books business class, comparing the price to an economy fare is worse than useless; it’s a false positive that erodes trust. The problem is that Google’s booking options don’t have a clean “cabin class” field. Or rather, they do. I just didn’t know where it was yet.

The first approach was pattern matching on brand names: parse “ECONOMY”, “DELTA ONE”, “FIRST”. This broke immediately. Alaska Airlines uses “Main.” JetBlue has “Blue” and “Even More.” “Qsuite” (Qatar Airways) matched “suite” and was classified as first class; it’s business. I tried three more approaches: a brand registry across 21 airlines, price matching within ±$1, and a sanity check at 40% of the base fare. Each broke.

The actual fix was a structural field I’d missed. Deep in the booking option data, at brand_block[6][0][0], there’s an integer: 1 for economy, 2 for premium economy, 3 for business, 4 for first. I tested it across 463 booking options, 11 routes, and multiple airlines. Zero misclassifications. Three days of work replaced by a single array index. The fix was there from the beginning, buried at the sixth level of a nested structure, waiting to be found.

The same pattern repeated with currency. I assumed prices were in cents. They’re not; Google doesn’t follow ISO 4217. I built three increasingly sophisticated currency conversion systems before discovering that the response includes a display price integer that’s always in the currency’s major unit. Always. Three iterations of currency code, deleted. I have never hit backspace with more satisfaction.

What Breaks

This codebase is built on an API that doesn’t know it exists. Google can change field positions at any time, without notice, and has done so. The decoder is paranoid by design: every field access is null-safe, every type is guarded, and corrupted itineraries are skipped rather than propagated.

A live canary runs against the actual Google Flights API in CI. Not to test swoop’s logic, but to detect format changes. If Google moves a field, the canary fails before any user-facing code runs against the new format. Then the hypothesis-testing framework re-runs against the new responses and identifies what moved. The test suite replays hundreds of captured responses offline as a regression corpus. It can verify that the parser handles data it’s seen before. It can’t guarantee anything about data it hasn’t seen.

Why Reverse-Engineering Google Flights Shouldn’t Be Necessary

2010Google acquires ITA Software for $700M
2010DOJ forces 5-year licensing obligation
2015Obligation expires
2018Google shuts down QPX Express API
2023American Airlines pulls 40% of fares from GDS
2026Amadeus kills self-service developer portal

Google has the most comprehensive flight pricing data in the world. They acquired it for $700 million, shared it under court order for five years, then stopped. Amadeus has had a self-service developer portal for years and is shutting it down. Skyscanner requires 100K monthly visitors to access the API. The walls keep going up.

I cite lawsuits in this industry, recognizing that swoop operates in the same gray area. And while calling undocumented endpoints at scale is not the same as scraping, it’s not the same as using a public API either. swoop makes the same HTTP requests a browser makes, with no authentication bypass. That’s not a defense. It’s a description of how thin the ice is. swoop exists because every legitimate path was either too expensive, too unreliable, or dead. That works for me, for now. But it shouldn’t be the only option.

But the fragility isn’t the point. Flight pricing is not a trade secret. It’s information that determines whether 4.5 billion annual air passengers get fair prices. The infrastructure to access it was built with public-interest obligations; the DOJ consent decree recognized that explicitly. Those obligations expired, and nothing replaced them.

What should exist is straightforward: a regulated, affordable API for flight pricing data, the way financial market data has EDGAR and real estate has MLS. The data is already aggregated. The infrastructure already works. The only missing piece is the requirement to share it. Instead, every startup that needs this data faces the same choice: spend millions on GDS access, or build something like swoop and hope the ice holds.

I built swoop in a week. The industry it navigates around has been closing doors for fifteen years.