A small CLI that answers where is flight X right now? for a flight number, ident, tail number, or FlightAware fa_flight_id.
uv run where BA437
uv run where --json BA437
It resolves the flight through FlightAware AeroAPI v4, picks the most relevant instance (airborne → next scheduled → most recent), and prints a compact summary. Times are shown in the airport’s local timezone and Europe/Rome. Optional OpenSky ADS-B is used only when AeroAPI has no position for an airborne flight.
Requires Python 3.11+ and uv.
git clone <this-repo>
cd where-flight
uv syncThat installs the where console script into the project environment.
| Variable | Required | Purpose |
|---|---|---|
AEROAPI_KEY |
yes | FlightAware AeroAPI key (x-apikey header) |
OPENSKY_CLIENT_ID |
no | OpenSky OAuth2 client id (client-credentials flow) |
OPENSKY_CLIENT_SECRET |
no | OpenSky OAuth2 client secret |
WHERE_CACHE_DIR |
no | Override the 60s JSON disk cache directory |
OpenSky is skipped entirely when either OpenSky variable is unset. Registration → ICAO24 uses a free hexdb lookup and is cached for 30 days.
Load a dotenv file without extra tooling:
export AEROAPI_KEY=...
# optional:
export OPENSKY_CLIENT_ID=...
export OPENSKY_CLIENT_SECRET=...
uv run where BA437
# or:
uv run --env-file .env where BA437AeroAPI is billed per result set. This tool is built for the Personal tier ($5/month credit, 10 result sets/minute):
- at most one
GET /flights/{ident}(plus inbound and position only when needed) - no polling
- no
/history/*endpoints - JSON responses cached on disk for 60 seconds (
~/.cache/where-flight)
Typical paid AeroAPI usage for one query is 1–3 result sets.
uv run where --help
uv run where BA437
uv run where --json BAW437
uv run where G-EUUUHuman output includes status, origin/destination, aircraft type and registration, live position when known, terminals/gates/baggage claim (or unknown when AeroAPI omits them), scheduled/estimated/actual times, delay, and the inbound aircraft (inbound_fa_flight_id) when FlightAware provides it — including whether that inbound is late relative to this departure. Tail swaps (inbound registration ≠ this flight’s registration) are called out.
--json prints the same information as structured JSON.
Unit tests do not call AeroAPI or OpenSky:
uv sync --group dev
uv run pytestLive check (only if you have a key; this spends AeroAPI credit):
export AEROAPI_KEY=...
uv run where BA437There is no live integration test in CI. Do not assume the CLI has been verified against production APIs unless AEROAPI_KEY was present when it was run.
- Prefer ICAO idents when you have them (
BAW437rather thanBA437). The CLI still accepts IATA flight numbers and passesident_type=designator. - Personal-tier date bounds are respected (
start≈ now−9d,end≈ now+2d). Lookups byfa_flight_idomit that window so an inbound leg is not clipped. - Missing gates, terminals, and baggage belts are reported as unknown rather than omitted silently.
- “Not operating today” is based on the origin airport timezone (Rome if the origin has no timezone).
Apache License 2.0. See LICENSE.