Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

✈️ flightsearch

Find the cheapest flight between two cities across an entire date range.

Most flight sites make you check one date at a time. flightsearch sweeps every day in the range you give it — "sometime in August" — and shows you the single cheapest departure, plus a price-per-day breakdown so you can see whether shifting your trip by a day saves you a hundred euros.

Русская версия README

CI Python License


Features

  • Whole-range search — give a start and end date, get the cheapest day.
  • One-way and round trip — for round trips you give a departure range and a return range, and get the cheapest combination of the two.
  • Price per day — full sorted list, so near-misses are visible too.
  • Filters — direct only, or at most 1 / 2 stops. For round trips both legs are checked, not just the outbound.
  • Currency and passengers picked in the UI.
  • Shareable links — every search is encoded in the URL, so a result can be bookmarked or sent to someone; opening the link re-runs the search.
  • Response caching — repeating a search does not spend your API quota.
  • Real aggregated data — prices come from the Travelpayouts / Aviasales aggregator, which continuously scans hundreds of agencies and airlines.
  • 4 languages — English, German, Russian, Ukrainian. Switches the whole UI, city names, date formats, and plural forms.
  • City autocomplete with IATA resolution — type "Vienna", "Wien" or "Вена", get VIE. Works without an API token.
  • Direct booking links to the actual ticket.
  • Zero dependencies — Python standard library only.
  • Demo mode — runs out of the box without any API key, so you can see the whole interface before signing up for anything.

Screenshots

Quick start

Requires Python 3.10 or newer. No packages to install.

git clone https://github.com/ostrvcxztd/flightsearch.git
cd flightsearch
python app.py

Open http://127.0.0.1:8000.

It starts in demo mode with generated placeholder prices. City search is already real. To get real prices, add a token — see below.

Enabling real prices

  1. Register for free at travelpayouts.com and copy your API token.

  2. Create your .env from the template:

    cp .env.example .env # Windows: copy .env.example .env
  3. Put the token in .env:

    TRAVELPAYOUTS_TOKEN=your_token_here
    
  4. Restart. The console will confirm real-data mode.

The token is read and used server-side only — it is never sent to the browser, so it cannot leak to users of your deployed instance.

Configuration

All settings live in .env (or real environment variables, which take precedence).

Variable Default Purpose
TRAVELPAYOUTS_TOKEN (empty) API token. Empty → demo mode.
TRAVELPAYOUTS_MARKER (empty) Partner ID for affiliate booking links.
FLIGHTSEARCH_CURRENCY eur Default currency; switchable in the UI.
FLIGHTSEARCH_CACHE_TTL 1800 Cache lifetime in seconds. 0 disables it.
FLIGHTSEARCH_HOST 127.0.0.1 Bind address. Use 0.0.0.0 to expose.
FLIGHTSEARCH_PORT 8000 Port.

Docker

docker build -t flightsearch .
docker run -p 8000:8000 -e TRAVELPAYOUTS_TOKEN=your_token_here flightsearch

The token is passed at run time and is deliberately not baked into the image.

HTTP API

The frontend is a thin client over two JSON endpoints — useful if you want to build your own UI.

GET /api/cities?term=<text>&locale=<ru|en|de|uk>

{ "cities": [ { "code": "VIE", "name": "Vienna", "country": "Austria" } ] }

GET /api/search

Parameter Required Notes
from, to yes IATA city codes, e.g. VIE, BCN
date_from, date_to yes Departure range, YYYY-MM-DD
trip no oneway (default) or round
return_from, return_to if round Return range
currency no eur usd rub uah gbp pln
passengers no 1–9, default 1
max_transfers no 0 = direct only; omit for any
{
 "demo": false,
 "origin": "VIE",
 "destination": "BCN",
 "round_trip": true,
 "currency": "EUR",
 "passengers": 2,
 "count": 13,
 "cheapest": {
 "date": "2026年08月19日",
 "return_date": "2026年08月25日",
 "price": 119,
 "currency": "EUR",
 "airline": "LW",
 "flight_number": "1532",
 "transfers": 1,
 "return_transfers": 0,
 "duration": 190,
 "duration_back": 150,
 "link": "https://www.aviasales.com/search/..."
 },
 "results": [ "... same shape, sorted by price ..." ]
}

transfers/duration describe the outbound leg; return_transfers/ duration_back describe the return leg and are null for one-way searches.

Invalid input returns HTTP 400 with {"error": "...", "error_code": "..."}. The error_code is stable and machine-readable (date_order, range_too_big, too_many_months, return_before_departure, no_cities, bad_dates, upstream) — the frontend uses it to show a translated message.

Development

pip install -e ".[dev]"
pytest tests/ -q # 47 tests, no network required
ruff check .

Tests stub out all external calls, so the suite runs offline and in CI.

Adding a language

  1. Add a block to the I18N object at the top of static/app.js.
  2. Add the language code to SUPPORTED_LOCALES in app.py.
  3. Add a button to #lang-switch in static/index.html.

Note that Russian and Ukrainian need three plural forms for "stops"; see the existing transfers functions.

Project layout

app.py HTTP server, search providers (real + demo), city lookup
static/index.html Page markup
static/style.css Styles
static/app.js i18n, autocomplete, search, rendering
tests/test_app.py Test suite
Dockerfile Container build

Limitations

  • Prices are per passenger. The aggregator quotes one adult; the passenger count is carried into the booking link rather than multiplied into the price, because fares are not always linear in group size. The UI labels the price accordingly.
  • Cached prices. The aggregator serves recently-seen prices, so the final price is confirmed by the seller at checkout and may differ slightly.
  • Free-tier rate limits. One API call per calendar month spanned by your range; for round trips it is one per (departure month ×ばつ return month) pair, capped at 16 per search. Responses are cached to keep this down.
  • Development server. app.py uses Python's built-in http.server, which is fine for personal and local use but is not hardened for public traffic. For a public deployment put it behind a reverse proxy (nginx, Caddy) or port the handler to a WSGI/ASGI server.

Contributing

Issues and pull requests are welcome. Please run pytest and ruff check . before opening a PR.

License

MIT © Maksym Yarmolenko

Not affiliated with Travelpayouts, Aviasales, or any airline.

About

Find the cheapest flight between two cities across an entire date range

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /