Last active
April 22, 2026 22:26
-
-
Save MomoDeve/a18053dea84dd28e320b8b2c489540eb to your computer and use it in GitHub Desktop.
Google Flights tfs URL Parameter Spec
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| syntax = "proto2"; | |
| package nomad.google_flights.research; | |
| // Reverse-engineered Google Flights `tfs` URL parameter. | |
| // | |
| // The `tfs` query value is URL-safe base64, usually without padding, over this | |
| // protobuf-style message. This is research, not an official Google contract. | |
| // Field names are descriptive and based on controlled Google Flights URL diffs. | |
| message GoogleFlightsTfs { | |
| // Example: every captured search encoded query_mode = 28. | |
| // | |
| // Meaning is still unknown. Synthetic changes did not produce visible UI | |
| // differences in testing; keep the value when generating compatible URLs. | |
| optional uint32 query_mode = 1; | |
| // Example: every captured search encoded query_context = 2. | |
| // | |
| // Meaning is still unknown. Synthetic changes did not produce visible UI | |
| // differences in testing; keep the value when generating compatible URLs. | |
| optional uint32 query_context = 2; | |
| // One leg for one-way, two reversed legs for round-trip, and N legs for | |
| // multi-city. Multi-city can encode, but does not reliably open with all legs | |
| // visible in the Google Flights UI. | |
| repeated FlightLeg legs = 3; | |
| // One entry per traveler. | |
| repeated PassengerType passengers = 8; | |
| optional Cabin cabin = 9; | |
| // Maximum displayed price amount. | |
| // | |
| // Example: | |
| // price up to 4000 CAD: max_price_amount = 4000 | |
| // | |
| // Currency is not encoded in this field. In tested URLs, changing gl=CA vs | |
| // gl=US changed the displayed currency context while leaving this amount as | |
| // the numeric ceiling. | |
| optional uint32 max_price_amount = 12; | |
| // Omitted for no bag filter. | |
| // | |
| // Google-generated carry-on samples currently observed: | |
| // 1 adult + 1 carry-on: field_2 = 1, field_3 = 0 | |
| // 2 adults + 2 carry-on: field_2 = 2, field_3 = 0 | |
| // 3 adults + 3 carry-on: field_2 = 3, field_3 = 0 | |
| // | |
| // Current best hypothesis: field_2 tracks selected carry-on count in natural | |
| // URLs, while field_3 stays 0 for these samples. | |
| // | |
| // Caution: synthetic probes with alternate combinations (for example | |
| // field_2 = 1/2 and field_3 = 0/1) did not always produce distinct visible | |
| // UI states, so carry-on vs checked semantics are still unresolved. | |
| optional BaggageFilter baggage = 13; | |
| // Example: every captured search encoded display_flag = 1. | |
| // | |
| // Meaning is still unknown. Synthetic changes did not produce visible UI | |
| // differences in testing; keep the value when generating compatible URLs. | |
| optional uint32 display_flag = 14; | |
| // Example: all_results_flag.sentinel = 18446744073709551615. | |
| optional AllResultsFlag all_results_flag = 16; | |
| // Example: | |
| // "Hide separate & self-transfer tickets" enabled: true | |
| // disabled: field omitted | |
| optional bool hide_separate_self_transfer_tickets = 17; | |
| optional TripType trip_type = 19; | |
| } | |
| message FlightLeg { | |
| // YYYY-MM-DD. | |
| optional string departure_date = 2; | |
| // Optional maximum stops filter. | |
| // | |
| // Presence matters because zero is meaningful: | |
| // omitted = no max-stops filter | |
| // 0 = nonstop only | |
| // 1 = 1 stop or fewer | |
| // 2 = 2 stops or fewer | |
| optional uint32 max_stops = 5; | |
| // Airline IATA codes, e.g. "AC" or "NH". | |
| repeated string include_airlines = 6; | |
| // Airline IATA codes to exclude. | |
| repeated string exclude_airlines = 7; | |
| // Time filters use whole-hour 24-hour buckets. | |
| // | |
| // Start fields match the visible start hour. End fields are the final | |
| // included hour bucket, so a visible upper bound of 20:00 encodes as 19. | |
| optional uint32 departure_start_hour = 8; | |
| optional uint32 departure_end_hour_inclusive = 9; | |
| optional uint32 arrival_start_hour = 10; | |
| optional uint32 arrival_end_hour_inclusive = 11; | |
| // Maximum total leg duration in minutes. | |
| // | |
| // Example: | |
| // "under 16h" = 960 | |
| optional uint32 max_duration_minutes = 12; | |
| optional Place origin = 13; | |
| optional Place destination = 14; | |
| // Connection airport IATA codes. | |
| // | |
| // Examples: | |
| // only Amsterdam as connection: include_connection_airports = "AMS" | |
| // Amsterdam + Calgary as connections: "AMS", "YYC" | |
| // exclude Amsterdam connection: exclude_connection_airports = "AMS" | |
| repeated string include_connection_airports = 15; | |
| repeated string exclude_connection_airports = 16; | |
| // Maximum layover duration in minutes. | |
| // | |
| // Example: | |
| // 13h30 = 810 | |
| optional uint32 max_layover_minutes = 18; | |
| // "Less emissions" filter. | |
| // | |
| // Example: | |
| // checkbox enabled: packed payload containing value 1 | |
| // | |
| // Modeled as packed numeric flags because the wire type is length-delimited, | |
| // not a plain varint bool. | |
| repeated uint32 emissions_flags = 19 [packed = true]; | |
| } | |
| message Place { | |
| // Examples: | |
| // 1 = exact airport, paired with a three-letter IATA code in entity_id | |
| // (YVR, CDG, YTZ, HND) | |
| // 2/3 = Google city/entity ids in /m/... form | |
| // | |
| // The difference between city entity types 2 and 3 is not clear enough to | |
| // encode as a stable enum. | |
| optional uint32 entity_type = 1; | |
| // Either an IATA code like "YVR" or a Google entity id like "/m/07dfk". | |
| optional string entity_id = 2; | |
| } | |
| message BaggageFilter { | |
| // Observed in natural Google-generated carry-on URLs: | |
| // field_2 = carry-on count (1, 2, 3 seen so far) | |
| // field_3 = 0 | |
| // | |
| // UI note from testing: carry-on count cannot exceed adult count. | |
| // | |
| // These are still research names, currently unknown | |
| optional uint32 field_2 = 2; | |
| optional uint32 field_3 = 3; | |
| } | |
| message AllResultsFlag { | |
| // Example: 18446744073709551615. | |
| // | |
| // This looks like an all-results or on-demand calculation sentinel. | |
| optional uint64 sentinel = 1; | |
| } | |
| // Companion `tfu` query parameter captured alongside some `tfs` URLs. | |
| // | |
| // It did not control passenger count, cabin, stop filters, airline filters, | |
| // time windows, or bag count in our current samples. Keep it separate from the | |
| // `tfs` contract until its role is clearer. | |
| message GoogleFlightsTfu { | |
| optional TfuState state = 2; | |
| } | |
| message TfuState { | |
| // Examples: | |
| // | |
| // tfu=EgYIABAAGAA: | |
| // sort = 0 | |
| // field_2 = 0 | |
| // field_3 = 0 | |
| // | |
| // tfu=EgoIABAAGAAgASgI: | |
| // sort = 0 | |
| // field_2 = 0 | |
| // field_3 = 0 | |
| // field_4 = 1 | |
| // field_5 = 8 | |
| // | |
| // cheapest mode with top-flights sort: | |
| // sort = 1 | |
| // field_4 = 2 | |
| // field_5 = 19 | |
| // | |
| // Sort examples: | |
| // top flights: sort = 1 | |
| // price: sort = 2 | |
| // departure time: sort = 3 | |
| // arrival time: sort = 4 | |
| // duration: sort = 5 | |
| // emissions: sort = 6 | |
| optional SortMode sort = 1; | |
| optional uint32 field_2 = 2; | |
| optional uint32 field_3 = 3; | |
| optional SearchMode search_mode = 4; | |
| // Companion state that changes in natural Google-generated Best/Cheapest | |
| // URLs, but does not appear to control the visible mode when changed alone. | |
| // | |
| // Examples: | |
| // Google-generated best/default mode: field_5 = 8 | |
| // Google-generated cheapest mode: field_5 = 19 | |
| // | |
| // Probes showed field_4 controls the visible Best/Cheapest mode: | |
| // field_4 = 2, field_5 = 8 -> cheapest | |
| // field_4 = 1, field_5 = 19 -> best | |
| // | |
| // Synthetic changes beyond those examples did not produce visible UI | |
| // differences in testing. Preserve Google-generated values when possible. | |
| optional uint32 field_5 = 5; | |
| } | |
| enum PassengerType { | |
| PASSENGER_TYPE_UNKNOWN = 0; | |
| PASSENGER_ADULT = 1; | |
| PASSENGER_CHILD = 2; | |
| PASSENGER_INFANT_ON_LAP = 3; | |
| PASSENGER_INFANT_IN_SEAT = 4; | |
| } | |
| enum Cabin { | |
| CABIN_UNKNOWN = 0; | |
| CABIN_ECONOMY = 1; | |
| CABIN_PREMIUM_ECONOMY = 2; | |
| CABIN_BUSINESS = 3; | |
| CABIN_FIRST = 4; | |
| } | |
| enum TripType { | |
| TRIP_TYPE_UNKNOWN = 0; | |
| TRIP_ROUND_TRIP = 1; | |
| TRIP_ONE_WAY = 2; | |
| TRIP_MULTI_CITY = 3; | |
| } | |
| enum SortMode { | |
| SORT_DEFAULT = 0; | |
| SORT_TOP_FLIGHTS = 1; | |
| SORT_PRICE = 2; | |
| SORT_DEPARTURE_TIME = 3; | |
| SORT_ARRIVAL_TIME = 4; | |
| SORT_DURATION = 5; | |
| SORT_EMISSIONS = 6; | |
| } | |
| enum SearchMode { | |
| SEARCH_MODE_UNKNOWN = 0; | |
| SEARCH_MODE_BEST = 1; | |
| SEARCH_MODE_CHEAPEST = 2; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment