# Crosswind | Google Flights CLI > Search Google Flights from your terminal. Built for humans and AI agents. ## Date Formats Crosswind accepts dates in many formats. All date arguments (departure and return) use the same parser. ### Supported formats | Format | Example | Resolves to | | ----------------- | ------------------------ | -------------------- | | Short month + day | `apr1`, `apr01`, `mar28` | 2026-04-01 | | Slash (no year) | `4/1`, `12/25` | Next occurrence | | Slash (with year) | `4/1/2026` | 2026-04-01 | | ISO 8601 | `2026-04-01` | 2026-04-01 | | Relative days | `+7`, `+30` | 7/30 days from today | | Named | `today`, `tomorrow` | Current/next day | | Month only | `apr`, `december` | 1st of that month | ### Future-biased resolution When you use a format without a year (like `apr1` or `4/1`), Crosswind picks the next occurrence: * If `apr1` is in the future this year, it uses this year * If `apr1` has already passed, it uses next year This means you never have to think about the year for upcoming trips. ### Examples ```bash # All of these work as the date argument crosswind BEG JFK apr1 crosswind BEG JFK 4/1 crosswind BEG JFK 4/1/2026 crosswind BEG JFK 2026-04-01 crosswind BEG JFK +7 crosswind BEG JFK tomorrow crosswind BEG JFK apr # Return dates use the same formats crosswind BEG JFK apr1 -r apr16 crosswind BEG JFK tomorrow -r +14 ``` ### Error messages If Crosswind cannot parse a date, it provides a helpful error: ``` error: cannot parse date 'xyz', try apr1, 4/1, +7, or 2026-04-01 ``` ## Filters Crosswind supports several filters to narrow search results. ### Cabin class Use `-c` or `--cabin` to select cabin class: ```bash crosswind BEG JFK apr1 -c economy # default crosswind BEG JFK apr1 -c premium # premium economy crosswind BEG JFK apr1 -c business crosswind BEG JFK apr1 -c first ``` Short aliases work too: `e`, `pe`, `b`, `f`. ### Maximum stops Use `--max-stops` to limit connections: ```bash crosswind LAX JFK apr1 --max-stops 0 # nonstop only crosswind LAX JFK apr1 --max-stops 1 # 1 stop max ``` Without this flag, all results are returned regardless of stop count. ### Passengers Use `-p` or `--adults` to set passenger count (1-9): ```bash crosswind BEG JFK apr1 -p 2 crosswind BEG JFK apr1 -p 4 ``` Prices returned reflect the per-person fare. ### Currency Use `--currency` to change the price currency: ```bash crosswind BEG JFK apr1 --currency EUR crosswind BEG JFK apr1 --currency GBP crosswind BEG JFK apr1 --currency RSD ``` Defaults to `USD`. Uses standard 3-letter ISO 4217 currency codes. ### Language Use `--lang` to change the Google Flights interface language: ```bash crosswind BEG JFK apr1 --lang de crosswind BEG JFK apr1 --lang fr ``` Defaults to `en`. This affects airport names and airline names in the results. ### Timeout Use `--timeout` to adjust the request timeout in seconds: ```bash crosswind BEG JFK apr1 --timeout 60 ``` Defaults to 30 seconds. Increase if you have a slow connection. ### Combining filters All filters compose naturally: ```bash crosswind BEG JFK apr1 -r apr16 -c business -p 2 --max-stops 1 --currency EUR ``` ## Multi-Destination Crosswind can search multiple destinations in a single command. ### Syntax Separate destination airport codes with commas: ```bash crosswind BEG JFK,LHR,CDG apr1 ``` This runs three separate searches (BEG→JFK, BEG→LHR, BEG→CDG), merges all results, and sorts by price. ### Use cases #### Comparing hubs Find the cheapest way to get from Belgrade to the US East Coast: ```bash crosswind BEG JFK,EWR,IAD apr1 ``` #### Exploring destinations Compare prices to multiple European cities: ```bash crosswind JFK LHR,CDG,AMS,FRA apr1 --currency EUR ``` #### Finding the cheapest option Pipe to jq to get just the cheapest flight across all destinations: ```bash crosswind BEG JFK,LHR,CDG apr1 | jq '.data.flights[0]' ``` ### How it works 1. Crosswind searches each destination sequentially 2. All flights are merged into a single result set 3. Airlines are deduplicated by code 4. Flights are sorted by price (ascending, unknown prices last) 5. The JSON envelope contains all flights from all destinations ### Limitations * Each destination adds one HTTP request, so multi-destination searches take proportionally longer * Google may rate-limit if you search too many destinations at once * The `--open` flag only opens the first destination in the browser ## Searching Flights Crosswind uses positional arguments for the most common parameters. No flag soup. ### Basic syntax ```bash crosswind ``` All three arguments are required. Origin and destination are 3-letter IATA airport codes. Date accepts [many formats](/usage/dates). ### One-way search ```bash crosswind BEG JFK apr1 crosswind LAX SFO tomorrow crosswind LHR CDG +7 ``` ### Round-trip search Add `-r` or `--ret` with a return date: ```bash crosswind BEG JFK apr1 -r apr16 crosswind LAX NRT 6/15 -r 6/30 ``` ### What you get In a terminal, Crosswind displays a styled table: ``` | # | Airlines | Route | Depart | Arrive | Duration | Stops | Price | |---|----------------|---------|------------------|------------------|----------|---------|-------| | 1 | LOT | BEG→JFK | 2026-04-01 06:15 | 2026-04-01 11:30 | 14h 15m | 1 stop | $627 | | 2 | Turkish Airlines| BEG→JFK | 2026-04-01 18:40 | 2026-04-02 08:15 | 18h 35m | 1 stop | $643 | ``` When piped to another program, it outputs a JSON envelope automatically: ```bash crosswind BEG JFK apr1 | jq '.data.flights | length' # 53 ``` ### Opening in browser Use `--open` to open the Google Flights search in your default browser instead of scraping: ```bash crosswind BEG JFK apr1 --open ``` Useful for booking. Crosswind finds the flights, you complete the purchase on Google Flights. ### Full flag reference | Flag | Short | Description | Default | | ------------- | ----- | --------------------------- | --------- | | `--ret` | `-r` | Return date (round-trip) | — | | `--cabin` | `-c` | Cabin class | `economy` | | `--adults` | `-p` | Passenger count | `1` | | `--max-stops` | — | Maximum stops (0 = nonstop) | — | | `--currency` | — | Currency code | `USD` | | `--lang` | — | Language code | `en` | | `--timeout` | — | Request timeout (seconds) | `30` | | `--json` | — | Force JSON output | — | | `--open` | — | Open in browser | — | ## Agent Integration Crosswind is designed for AI agents and automation from the ground up. ### stdout/stderr contract * **stdout** = structured data only (JSON or table) * **stderr** = human diagnostics (timing, hints) Your agent captures stdout for data. Exit codes provide semantic status without parsing. ### Recommended agent pattern ```bash # 1. Search with JSON output RESULT=$(crosswind BEG JFK apr1 --json 2>/dev/null) EXIT=$? # 2. Check exit code if [ $EXIT -ne 0 ]; then ERROR=$(echo "$RESULT" | jq -r '.message') HINT=$(echo "$RESULT" | jq -r '.hint // empty') # Handle error based on exit code fi # 3. Process results FLIGHTS=$(echo "$RESULT" | jq '.data.flights | length') CHEAPEST=$(echo "$RESULT" | jq '.data.flights[0].price') ``` ### JSON envelope Every successful response: ```json { "v": 1, "status": "ok", "cmd": "search", "data": { "flights": [...], "airlines": [...] }, "timing_ms": 1423 } ``` Every error response: ```json { "v": 1, "status": "error", "code": "rate_limited", "message": "rate limited by Google, wait a few minutes or use --proxy", "hint": "wait a few minutes before retrying, or use --proxy" } ``` ### Exit codes for agents | Code | Meaning | Agent action | | ---- | ------------ | ------------------------------- | | `0` | Success | Parse `data` | | `2` | Bad input | Fix arguments, do not retry | | `3` | Network | Retry with backoff | | `4` | Rate limited | Wait 2-5 min, then retry | | `5` | Parse error | Report to user, may need update | | `1` | Other | Log and report | ### Example: cheapest flight finder ```bash #!/bin/bash # Find the cheapest nonstop flight from BEG to any of these cities DESTS="JFK,LHR,CDG,AMS,FRA" RESULT=$(crosswind BEG "$DESTS" +7 --max-stops 0 --json 2>/dev/null) if [ $? -ne 0 ]; then echo "Search failed: $(echo "$RESULT" | jq -r '.message')" exit 1 fi echo "$RESULT" | jq -r ' .data.flights[0] | "Cheapest: \(.segments[0].from_code)→\(.segments[-1].to_code) $\(.price) via \(.airlines | join("/")) (\(.duration_minutes / 60 | floor)h)" ' ``` ### Example: price monitoring ```bash #!/bin/bash # Log cheapest BEG→JFK price daily PRICE=$(crosswind BEG JFK +30 --json 2>/dev/null | jq '.data.flights[0].price') echo "$(date +%Y-%m-%d),$PRICE" >> prices.csv ``` ### Rate limiting Google may rate-limit or block if you make too many requests. Recommendations: * Space requests at least 10 seconds apart * Randomize timing for scheduled tasks * Monitor exit code 4 and back off when detected * Consider using `--timeout 60` for reliability :::tip[Agent-friendly by design] The stdout/stderr split, semantic exit codes, and structured JSON envelopes mean your agent never has to parse human-readable text. Check the exit code, parse the JSON, act on the data. ::: ## Exit Codes Crosswind uses semantic exit codes so scripts and agents can handle errors without parsing output. ### Exit code table | Code | Category | Description | | ---- | -------------------- | --------------------------------------------------- | | `0` | Success | Flights found and returned | | `1` | General error | Unexpected HTTP status, internal error | | `2` | Validation | Bad airport code, invalid date, bad passenger count | | `3` | Network | Timeout, connection failed, DNS error, TLS error | | `4` | Rate limit / blocked | Google rate limit (429), bot detection (403/503) | | `5` | Parse error | Page structure changed, no results found | ### Reason codes In JSON error envelopes, the `code` field provides a machine-readable reason: | Reason code | Exit code | Description | | ---------------------- | --------- | ----------------------------- | | `invalid_airport_code` | 2 | Not a 3-letter IATA code | | `invalid_date` | 2 | Cannot parse date input | | `invalid_passengers` | 2 | Passenger count out of range | | `timeout` | 3 | Request timed out | | `connection_failed` | 3 | Could not connect | | `dns_resolution` | 3 | DNS lookup failed | | `tls_error` | 3 | TLS handshake failed | | `proxy_error` | 3 | Proxy connection failed | | `rate_limited` | 4 | HTTP 429 from Google | | `blocked` | 4 | HTTP 403/503, bot detection | | `script_tag_not_found` | 5 | Expected data not in page | | `parse_error` | 5 | Failed to extract flight data | | `no_results` | 5 | No flights for this search | | `http_status` | 1 | Unexpected HTTP status | | `other` | 1 | Catch-all | ### Usage in scripts ```bash crosswind BEG JFK apr1 --json > flights.json code=$? case $code in 0) echo "Success" ;; 2) echo "Bad input, check arguments" ;; 3) echo "Network issue, retry later" ;; 4) echo "Rate limited, wait and retry" ;; 5) echo "Parse error, Google may have changed" ;; *) echo "Unknown error" ;; esac ``` ### Hints Some errors include hints in both TTY and JSON modes: ``` error: airport code must be exactly 3 letters, got 'XX' hint: use a 3-letter IATA code like JFK, LAX, BEG ``` In JSON mode, hints appear as an optional `hint` field in the error envelope. ## Limitations Crosswind is an early release. This page documents known limitations and risks. ### Google dependency Crosswind scrapes Google Flights. This means: * **No API guarantee.** Google can change their page structure at any time, which would break parsing (exit code 5). * **Rate limiting.** Too many requests will trigger HTTP 429 responses. Space your requests. * **Bot detection.** Google may block requests that appear automated, returning 403 or 503 (exit code 4). * **Regional variation.** Results may differ based on your IP location and the `--lang`/`--currency` settings. ### Not a booking tool Crosswind is read-only. It searches and displays flight information but cannot book flights. Use `--open` to continue on Google Flights in your browser. ### Data accuracy * Prices shown are the prices Google Flights reports at search time. They may change by the time you book. * Some flights may show `$0` price if Google doesn't have pricing data for that result. * Carbon emission estimates are provided by Google and may not be available for all flights. ### Missing features (planned) These features are planned for future releases: * **Price alerts**: monitor a route and notify on price drops * **Sorting options**: sort by duration, departure time, etc. * **SOCKS proxy support**: route requests through a proxy (the dependency supports it, CLI flag not yet exposed) * **Multi-city itineraries**: A→B→C style trips (not same as multi-destination comparison) ### Platform notes * **TLS fingerprinting.** Crosswind impersonates Chrome's TLS fingerprint. This is required for Google to return full flight data. Without it, Google returns an empty page shell. * **Binary size.** The release binary is \~15-20MB due to TLS and HTTP dependencies. This is expected for a statically-linked Rust binary with Chrome TLS impersonation. * **Protobuf encoding.** Search parameters are encoded as Protocol Buffers, matching Google Flights' internal format. If Google changes their protobuf schema, the search encoding would need to be updated. ### Reporting issues If Crosswind stops working (especially exit code 5 errors), Google may have changed their page structure. Please report issues at: [github.com/dzmbs/crosswind/issues](https://github.com/dzmbs/crosswind/issues) Include the command you ran, the exit code, and the error message. ## Output Modes Crosswind separates data from diagnostics and supports automatic format detection. ### The contract * **stdout** = flight data (JSON, tables) * **stderr** = diagnostics (timing, hints, error messages in TTY mode) This separation means you can safely pipe Crosswind's output into `jq` or other tools without diagnostic noise contaminating the data stream. ### Auto-detection When you do not specify a format, Crosswind auto-detects: * TTY stdout → styled table * Non-TTY stdout (pipe, redirect) → JSON envelope #### Force JSON in a terminal ```bash crosswind BEG JFK apr1 --json ``` ### JSON envelope All JSON output follows a consistent envelope: ```json { "v": 1, "status": "ok", "cmd": "search", "data": { "flights": [...], "airlines": [...] }, "timing_ms": 1423 } ``` | Field | Description | | ----------- | ------------------------------------------- | | `v` | Envelope version (always `1`) | | `status` | `"ok"` or `"error"` | | `cmd` | Command name (`"search"`) | | `data` | Search results (flights + airline metadata) | | `timing_ms` | Request duration in milliseconds | ### Flight object Each flight in `data.flights` contains: ```json { "airlines": ["LOT"], "segments": [...], "price": 627, "stops": 1, "duration_minutes": 855, "is_best": true, "carbon_grams": 284000, "typical_carbon_grams": 310000 } ``` ### Segment object Each segment in a flight: ```json { "from_code": "BEG", "from_name": "Belgrade Nikola Tesla Airport", "to_code": "WAW", "to_name": "Warsaw Chopin Airport", "depart_date": "2026-04-01", "depart_time": "06:15", "arrive_date": "2026-04-01", "arrive_time": "07:45", "duration_minutes": 90, "aircraft": "Embraer 195", "flight_number": "LO 572" } ``` ### Error envelope When an error occurs in JSON mode: ```json { "v": 1, "status": "error", "code": "invalid_airport_code", "message": "airport code must be exactly 3 letters, got 'XX'", "hint": "use a 3-letter IATA code like JFK, LAX, BEG" } ``` ### Piping with jq ```bash # Count flights crosswind BEG JFK apr1 | jq '.data.flights | length' # Cheapest flight crosswind BEG JFK apr1 | jq '.data.flights[0]' # All nonstop flights crosswind BEG JFK apr1 | jq '[.data.flights[] | select(.stops == 0)]' # Flight numbers only crosswind BEG JFK apr1 | jq '[.data.flights[].segments[].flight_number]' # Best flights under $800 crosswind BEG JFK apr1 | jq '[.data.flights[] | select(.is_best and .price < 800)]' ``` ## Getting Started Install Crosswind and run your first flight search in under a minute. :::info[What is Crosswind?] Crosswind is a CLI tool that searches Google Flights from your terminal. It uses positional arguments, smart date parsing, and outputs structured JSON. Designed for both humans and AI agents. ::: ### 1. Install Crosswind ```bash # One-command install (recommended) curl -fsSL https://raw.githubusercontent.com/dzmbs/crosswind/main/install.sh | bash # Or install with cargo cargo install --git https://github.com/dzmbs/crosswind --bin crosswind ``` Confirm it works: ```bash crosswind --version crosswind --help ``` See [Installation](/introduction/installation) for other methods and platform details. ### 2. Search for flights ```bash # One-way: Belgrade to New York, April 1st crosswind BEG JFK apr1 # Round-trip with return date crosswind BEG JFK apr1 -r apr16 ``` Crosswind uses 3-letter IATA airport codes. The date accepts many formats: `apr1`, `4/1`, `+7`, `tomorrow`, or `2026-04-01`. ### 3. Filter results ```bash # Nonstop flights only crosswind LAX JFK apr1 --max-stops 0 # Business class, 2 passengers crosswind BEG LHR apr1 -c business -p 2 # Prices in EUR crosswind BEG CDG apr1 --currency EUR ``` ### 4. Get JSON output When piped, Crosswind automatically outputs JSON. Force it in a terminal with `--json`: ```bash # Auto JSON when piped crosswind BEG JFK apr1 | jq '.data.flights[0]' # Force JSON in terminal crosswind BEG JFK apr1 --json ``` The JSON envelope follows a consistent structure: ```json { "v": 1, "status": "ok", "cmd": "search", "data": { "flights": [...], "airlines": [...] }, "timing_ms": 1423 } ``` ### 5. Compare destinations Search multiple destinations in one command: ```bash crosswind BEG JFK,LHR,CDG apr1 ``` Results are merged and sorted by price. ### 6. Open in browser Preview the search on Google Flights directly: ```bash crosswind BEG JFK apr1 --open ``` ### Next steps * [Date Formats](/usage/dates) * [Filters](/usage/filters) * [Output Modes](/reference/output) * [Agent Integration](/reference/agent-integration) ## Installation ### Quick install (recommended) The installer detects your OS and architecture, downloads a prebuilt binary, and falls back to cargo if needed. ```bash curl -fsSL https://raw.githubusercontent.com/dzmbs/crosswind/main/install.sh | bash ``` By default, the binary is installed to `~/.local/bin`. Override with `CROSSWIND_INSTALL_DIR`: ```bash CROSSWIND_INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/dzmbs/crosswind/main/install.sh | bash ``` ### From source (cargo install) Requires Rust 1.88 or later. #### From Git ```bash cargo install --git https://github.com/dzmbs/crosswind --bin crosswind ``` #### From a local checkout ```bash git clone https://github.com/dzmbs/crosswind.git cd crosswind cargo install --path . ``` ### Pre-built binaries (manual) Download the latest release tarball for your platform from the [releases page](https://github.com/dzmbs/crosswind/releases). ```bash # Example for macOS arm64 tar xzf crosswind-v0.2.0-macos-aarch64.tar.gz sudo mv crosswind /usr/local/bin/ ``` Or place it anywhere on your `PATH`: ```bash mkdir -p ~/.local/bin mv crosswind ~/.local/bin/ ``` ### Verify installation ```bash crosswind --version crosswind --help ``` ### Supported platforms | Platform | Architecture | Status | | -------- | --------------------- | ----------- | | macOS | arm64 (Apple Silicon) | Supported | | macOS | x86\_64 (Intel) | Supported | | Linux | x86\_64 | Supported | | Linux | arm64 | Best-effort | | Windows | x86\_64 | Best-effort | "Best-effort" means it should work but is not part of the primary test matrix. ### Build from source ```bash cargo build --release # Binary is at target/release/crosswind ``` :::tip[Rust version] Crosswind requires Rust 1.88 or later. Run `rustup update stable` to get the latest stable toolchain. :::