Inspiration

The classic flight search experience assumes you already know what you want: origin, destination, exact dates. That's fine if you're booking a Tuesday work trip to Berlin. It's useless if the idea in your head is closer to "somewhere quiet in the Alps, a bit off the beaten path, mid-May."

There are tools that take a fuzzy prompt and ask an LLM what to do with it, but the answers aren't grounded in real prices, real dates, or real hotels. You get a vibe back, not a trip. We wanted to close that loop. Type the vibe, get something you can actually book.

What it does

You type whatever's in your head. "Weekend in Slovenia for two, around €300 total." "Niche countryside in the Alps." "Somewhere warm, I'm flexible on when." VibeTravel does roughly five things:

  1. Pulls structured intent out of your sentence: places, countries, dates, budget, vibe, traveler count.
  2. Fans out across every candidate destination it can resolve and fetches indicative roundtrip flights.
  3. Searches for hotels in each destination with availability for those dates.
  4. Ranks hotels by how well their description, amenities, and top reviews match your vibe — blended with price and guest rating.
  5. Hands you back grouped flight + hotel cards with deal links that deep-link into the right search on Skyscanner and the right property on Booking.com, with dates and travelers pre-filled.

If something's missing (no dates, ambiguous destination, no flights for that window), the app halts and asks you a clarifying question instead of dumping garbage on you. Refinements like "narrow to Italy" or "direct flights only" are folded back into the previous state, so the conversation actually moves forward.

How we built it

The backend is a Python pipeline with four stages:

Extract intent → Search flights → Search hotels → Group results

Each stage reads the full trip state and writes back only what it changed. Any stage can pause and ask the user a clarifying question; the frontend re-submits the previous state along with the user's reply, so the pipeline picks up where it left off rather than starting over.

The frontend is Next.js and React. The browser never talks to the Python service directly — a thin route handler proxies requests, which keeps the API surface clean and lets us swap backends later without touching any UI code.

Hotel ranking works by turning each hotel's description, amenities, and top reviews into a semantic fingerprint, doing the same for your vibe phrase, and scoring how closely they match — weighted with price and guest rating so a cheap-but-bad hotel doesn't beat a great-but-pricey one on price alone.

There's also a full mock mode that runs the entire pipeline with synthetic data and no API keys required. That saved us a lot of grief during local development and let us demo from planes and cafés without burning quota.

Challenges we ran into

Way more than we expected, and almost all of them in boring places.

  • The first version only ever searched one destination. Type "Alps for three people" and you'd get options for whatever single airport we resolved first. Everything else got dropped on the floor. We fixed it by iterating over every resolved place and tagging each result with its destination.
  • "Narrow to Italy" doing absolutely nothing. Clarification turns originally re-ran from scratch and ignored the previous state. The user would refine and get the same French ski resorts back. The fix was threading the previous state back into every clarification so the new query applies on top of the existing intent.
  • Booking.com deal links landing on "no rooms" pages. Two bugs stacked: the API returns properties with no availability (with a null price), and we were building Booking URLs as a fuzzy text search on the hotel name. So users would click a deal and land on a different, also-unavailable property. We now drop unavailable hotels and deep-link directly to the property page.
  • Skyscanner showing "1 adult" regardless of what we asked for. Turns out Skyscanner's search page reads one parameter name, not the one we were setting. Hours of debugging, one query parameter rename.
  • Prices off by a factor of three. One API returns per-person prices, another returns totals. Our cards were mixing the two. We now normalize everything to a total before it leaves the flight stage.
  • "Dolomites" returning nothing. Booking classifies the Dolomites as a region, not a city, and our resolver only accepted cities. We now walk a fallback chain through region, district, landmark, and country.
  • Date-less queries generating absurd hotel searches. When no dates were given, the pairing logic was happy to match an outbound in May with a return in November, which became a hotel search for a 200-day stay. We capped trip length and made the hotel stage fall back to dates from the matched flight.
  • A merge that almost ate the project. Late in the build, two branches had diverged in non-trivial ways. We had to manually integrate the multi-destination feature while keeping the price-scaling and roundtrip fixes from the other branch. Worth it, but stressful.

Accomplishments that we're proud of

  • The pipeline is genuinely plug-and-play. New stages drop in with no edits to existing ones, and the shared state makes them feel composable rather than tangled.
  • Mock mode runs the entire pipeline with zero API keys. We could demo and iterate on UX with no internet, which mattered more than we want to admit.
  • The deal links actually work. Click "View deal" and Skyscanner opens with the right airports, dates, and traveler count, and Booking opens directly on the property page for the right window. Sounds trivial. Took about ten iterations to get there.
  • Vibe ranking feels right. "Quiet mountain escape, nothing too touristy" really does float small auberges and family-run lodges over big resort hotels, with no keyword hacking.
  • 43 unit tests covering the full pipeline, intent extraction, multi-destination flight fan-out, hotel availability filtering, and per-person price scaling. All green.

What we learned

  • Intent extraction is the easy part. The hard part is everything that happens after: turning a fuzzy intent into real API calls that don't break on edge cases, and presenting results without lying to the user.
  • Deep links are most of the perceived quality. A working deep link feels like magic. A broken one feels like a bait-and-switch. We spent way more time on URL building than we expected, and we'd do it again.
  • Vibe matching is cheap, fast, and disturbingly good at soft ranking. It beats keyword matching by a wide margin and doesn't need any special infrastructure at this scale.
  • Stateless clarification is a trap. Always echo the previous state back through the pipeline, or your users feel like the system has amnesia.
  • Mock-first is a superpower. Building the whole backend behind interfaces with a synthetic client meant we could iterate on flow and UI logic without ever paying for an API call.

What's next for VibeTravel

  • Pre-indexed hotel corpus. Right now hotels are matched on the fly per request. Pre-indexing a curated set of destinations would make this fast at real scale.
  • Live prices on the cards. We show indicative prices and reserve live lookups for the deal link. Running live lookups for the top few candidates would make the price on the card match what the user actually sees on click.
  • A "why this one?" line per card. We want the app to write a one-liner per result that quotes the user's own vibe words back at them ("quiet, off the beaten path, wood-burning stove"), so the ranking is legible, not magical.
  • More inventory. A second hotel provider, and a train option for short European trips, would make the experience much richer for the use case we keep finding ourselves in.
  • Group coordination. Long shot, but: shared sessions where two or three friends each thumbs-up / thumbs-down options and the ranker folds everyone's signal in. Closer to how trips actually get planned.
  • Enrich destination data Based on the maps data, plan is to include venues courts, clubs into the vibeb matching flow

Built With

Share this project:

Updates