# FetchFlight
[](https://hex.pm/packages/fetch_flight)
[](https://hexdocs.pm/fetch_flight)
Elixir port of the Python [`fast_flights`](https://github.com/AWeirdDev/flights) library. Scrapes Google Flights by encoding a query as a protobuf binary, fetching the results page, and returning structured flight data — no API key required.
Two entry points:
- `get_flights/2` — fetch specific itineraries for a given date
- `get_price_graph/1` — fetch a calendar of cheapest prices across a date range
## Installation
```elixir
def deps do
[
{:fetch_flight, "~> 0.2"}
]
end
```
## Usage
### `get_flights/2` — flight search
```elixir
query = %{
data: [
%{
date: "2026-05-01",
from_airport: %{code: "SFO"},
to_airport: %{code: "JFK"},
max_stops: nil,
airlines: []
}
],
seat: :economy,
trip: :one_way,
passengers: [:adult]
}
{:ok, {metadata, flights}} = FetchFlight.get_flights(query)
```
### Options
```elixir
FetchFlight.get_flights(query, language: "en", currency: "USD")
```
| Option | Default | Description |
|--------|---------|-------------|
| `:language` | `"en"` | BCP 47 language tag |
| `:currency` | `"USD"` | ISO 4217 currency code |
### Query fields
**Top-level**
| Field | Type | Values |
|-------|------|--------|
| `:seat` | atom | `:economy`, `:premium_economy`, `:business`, `:first` |
| `:trip` | atom | `:one_way`, `:round_trip`, `:multi_city` |
| `:passengers` | `[atom]` | `:adult`, `:child`, `:infant_in_seat`, `:infant_on_lap` |
| `:data` | `[flight_data]` | One entry per leg |
**Per leg (`data` list)**
| Field | Type | Notes |
|-------|------|-------|
| `:date` | `"YYYY-MM-DD"` | Departure date |
| `:from_airport` | `%{code: "SFO"}` | IATA origin code |
| `:to_airport` | `%{code: "JFK"}` | IATA destination code |
| `:max_stops` | `integer \| nil` | `nil` = any number of stops |
| `:airlines` | `[String.t()]` | Filter by IATA airline code, e.g. `["UA", "AA"]` |
### Multi-city example
```elixir
query = %{
data: [
%{date: "2026-06-01", from_airport: %{code: "SFO"}, to_airport: %{code: "LHR"}, max_stops: nil, airlines: []},
%{date: "2026-06-10", from_airport: %{code: "LHR"}, to_airport: %{code: "CDG"}, max_stops: 0, airlines: []}
],
seat: :business,
trip: :multi_city,
passengers: [:adult, :adult]
}
```
### `get_price_graph/1` — price calendar
Returns the cheapest price for each departure date in a range — useful for finding the best days to fly.
```elixir
query = %{
range_start_date: "2026-05-01",
range_end_date: "2026-05-31",
trip_length: 7,
src_airports: ["SFO"],
dst_airports: ["JFK"]
}
{:ok, offers} = FetchFlight.get_price_graph(query)
# With a different currency
{:ok, offers} = FetchFlight.get_price_graph(query, currency: "EUR")
```
### Options
```elixir
FetchFlight.get_price_graph(query, currency: "EUR")
```
| Option | Default | Description |
|--------|---------|-------------|
| `:currency` | `"USD"` | ISO 4217 currency code |
Results are sorted by `start_date` ascending:
```
[
%FetchFlight.PriceGraphOffer{start_date: "2026-04-17", return_date: "2026-04-24", price: 219.0},
%FetchFlight.PriceGraphOffer{start_date: "2026-04-18", return_date: "2026-04-25", price: 246.0},
%FetchFlight.PriceGraphOffer{start_date: "2026-05-01", return_date: "2026-05-08", price: 231.0},
...
]
```
> **Note:** Google's Price graph covers roughly ±5 weeks around the search date, so results may extend slightly outside the requested range.
#### Price graph query fields
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `:range_start_date` | `"YYYY-MM-DD"` | yes | First departure date to check |
| `:range_end_date` | `"YYYY-MM-DD"` | yes | Last departure date to check |
| `:trip_length` | `integer` | yes | Length of stay in days |
| `:src_airports` | `[String.t()]` | one of src/dst required | Origin IATA codes |
| `:src_cities` | `[String.t()]` | one of src/dst required | Origin city names |
| `:dst_airports` | `[String.t()]` | one of src/dst required | Destination IATA codes |
| `:dst_cities` | `[String.t()]` | one of src/dst required | Destination city names |
| `:trip` | atom | no | `:round_trip` (default), `:one_way` |
| `:seat` | atom | no | `:economy` (default), `:premium_economy`, `:business`, `:first` |
| `:passengers` | `[atom]` | no | Same values as `get_flights/2`, defaults to `[:adult]` |
| `:stops` | atom | no | `:any` (default), `:nonstop`, `:one_stop`, `:two_stops` |
## Output
### `get_flights/2`
```elixir
{:ok, {%FetchFlight.JsMetadata{airlines: [...], alliances: [...]}, flights}}
```
`flights` is a list of `%FetchFlight.Flights{}`, sorted cheapest first:
```
%FetchFlight.Flights{
type: "DL", # IATA carrier code
price: 344, # in the requested currency
airlines: ["Delta"],
carbon: %FetchFlight.CarbonEmission{
emission_grams: 284_000,
typical_on_route_grams: 354_000
},
flights: [
%FetchFlight.SingleFlight{
from_airport: %FetchFlight.Airport{code: "SFO", name: "San Francisco International Airport"},
to_airport: %FetchFlight.Airport{code: "JFK", name: "John F. Kennedy International Airport"},
departure: %FetchFlight.SimpleDatetime{date: [2026, 5, 1], time: [7, 0]},
arrival: %FetchFlight.SimpleDatetime{date: [2026, 5, 1], time: [15, 28]},
duration_minutes: 328,
plane_type: "Boeing 767"
}
]
}
```
**`SimpleDatetime` format**
- `:date` — `[year, month, day]`
- `:time` — `[hour, minute]`, or `[hour]` when minutes = 0
### `get_price_graph/1`
```elixir
{:ok, [%FetchFlight.PriceGraphOffer{}, ...]}
```
Each `%FetchFlight.PriceGraphOffer{}` has:
| Field | Type | Description |
|-------|------|-------------|
| `:start_date` | `String.t()` | Departure date (`"YYYY-MM-DD"`) |
| `:return_date` | `String.t()` | Return date (`"YYYY-MM-DD"`), `nil` for one-way |
| `:price` | `float()` | Cheapest price found for that departure date |
## How it works
### `get_flights/2`
1. **Encode** — the query is serialized as a protobuf binary (manually encoded, no generated code) and Base64-encoded into the `tfs` URL parameter.
2. **Fetch** — `GET https://www.google.com/travel/flights?tfs=...&hl=...&curr=...` with realistic browser headers via [`req`](https://github.com/wojtekmach/req).
3. **Parse** — [`floki`](https://github.com/philss/floki) locates the `<script class="ds:1">` tag; [`jason`](https://github.com/michalmuskala/jason) decodes the embedded JSON payload; specific array indices are navigated to extract flights, prices, and carbon data.
### `get_price_graph/1`
1. **Encode** — the query (both outbound and return legs) is serialized as a protobuf binary and Base64-encoded into the `tfs` URL parameter, same as `get_flights/2`. Including both legs tells Google the intended trip length.
2. **Navigate** — a headless Chromium browser (via [`playwright`](https://hex.pm/packages/playwright)) loads the Google Flights results page, dismisses any deal popup, scrolls to the "Prices for nearby dates" section, and clicks the "Price graph" tab.
3. **Intercept** — a response listener captures the `GetCalendarGraph` XHR that fires when the Price graph tab is clicked. This endpoint returns ~10 weeks of daily prices, one entry per departure date at exactly the requested trip length.
4. **Parse** — the response (prefixed with `)]}'\n`) is decoded with `jason`. The outer envelope is unwrapped, the inner JSON string is decoded, and `[departure_date, return_date, [[null, price], token], 1]` entries are filtered to the requested date range and sorted by departure date.
## Disclaimer
This library scrapes a public web interface. Google may change the page structure without notice, which could break parsing. It is intended for personal and research use — check Google's Terms of Service before deploying at scale.
## License
MIT