Search flights and stays, then create card or crypto payments via TravAI. Use when the user asks about flights, travel planning, booking trips, finding accom...
---
name: TravAI
description: Search flights and stays, then create card or crypto payments via TravAI. Use when the user asks about flights, travel planning, booking trips, finding accommodation, or travel inspiration.
---
# TravAI
Use this skill when the user wants to search travel options or book a flight/stay through TravAI.
## Source of truth
If this skill conflicts with backend behavior, check the live OpenAPI schema:
```
https://api.travai.tech/openapi.json
```
Follow the live schema over this doc.
## Critical notes
- Search responses return `search_id` (not `search_hash`)
- Payment requests use `method` (`"CRYPTO"` or `"CARD"`) — not `type`
- Searches and offers **expire**; if any request fails with "not found" or `EXPIRED_OFFER`, re-run the search and use fresh IDs
- **Offer IDs are unique per search** — after re-running a search, you MUST use the new `offer_id` values from the new results; old offer IDs will not work with the new `search_id`
- For crypto payments, always return the **exact** amount, token, deposit address, and refund wallet — never round
- **Changes and cancellations** are not available via the API — direct users to support: https://t.me/travaiofficial or support@travai.tech
---
## API base
```
https://api.travai.tech
```
## Authentication
All endpoints (except sign-in and sign-up) require:
```
Authorization: Bearer {access_token}
```
Three ways to authenticate:
1. **Sign up** — new user
2. **Sign in** — returning user
3. **Paste token** — user copies from [https://app.travai.tech/profile](https://app.travai.tech/profile)
### Sign In
```
POST /auth/signin
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret"
}
```
Response (200):
```json
{
"access_token": "eyJhbG...",
"token_type": "bearer"
}
```
### Sign Up
```
POST /auth/signup
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret",
"first_name": "Jane",
"last_name": "Doe"
}
```
Response (200):
```json
{
"message": "User created successfully",
"access_token": "eyJhbG...",
"token_type": "bearer"
}
```
### Get current user
```
GET /auth/me
Authorization: Bearer {access_token}
```
Response (200):
```json
{
"user_id": 1,
"email": "user@example.com",
"role": "customer",
"is_active": true,
"created_at": "2025-01-01T00:00:00",
"currency": "USD"
}
```
### Error responses
Any failed request may return:
```json
{ "detail": "Human-readable error description" }
```
Always check for `detail` in non-2xx responses and surface the message to the user.
---
## Endpoints
### Search flights
```
POST /searches
Authorization: Bearer {access_token}
Content-Type: application/json
{
"type": "FLIGHT",
"origin_iata": "LON",
"destination_iata": "LIS",
"departure_date": "2025-10-10",
"return_date": "2025-10-17",
"customers": [
{ "type": "ADULT" },
{ "type": "CHILD", "age": 10 }
]
}
```
| Field | Required | Notes |
|---|---|---|
| `type` | yes | `"FLIGHT"` |
| `origin_iata` | yes | 3-letter IATA code (uppercase, e.g. `"LON"`) |
| `destination_iata` | yes | 3-letter IATA code (uppercase, e.g. `"LIS"`) |
| `departure_date` | yes | `YYYY-MM-DD` |
| `return_date` | no | omit for one-way |
| `customers` | yes | array of `CustomerAge` (see below) |
**`CustomerAge` object:**
| Field | Required | Notes |
|---|---|---|
| `type` | no (default: `ADULT`) | `ADULT`, `CHILD`, `SENIOR`, `HELD_INFANT` |
| `age` | required for `CHILD` and `HELD_INFANT` | integer |
Customer type rules:
- `HELD_INFANT` — under 2 years old
- `CHILD` — between 2 and 18 years old
- `ADULT` — default
- `SENIOR` — senior traveller
Response (200):
```json
{
"search_id": "abc123",
"total_offers": 42,
"best_offers": [ /* top 3 offers */ ]
}
```
| Field | Description |
|---|---|
| `search_id` | Identifier for this search — use in all subsequent calls |
| `total_offers` | Total number of offers matching the search |
| `best_offers` | The 3 best offers (sorted by price/quality) |
**Direct-flight filtering:** To find direct flights, only keep offers where the total segment count across all slices equals the number of slices (i.e. 1 segment per slice = no stops).
### Search stays
```
POST /searches
Authorization: Bearer {access_token}
Content-Type: application/json
{
"type": "STAY",
"location_anchor": {
"value": "Abu Dhabi",
"type": "city"
},
"check_in_date": "2026-03-05",
"check_out_date": "2026-03-10",
"rooms": 1,
"guests": 1,
"radius_km": 50
}
```
| Field | Required | Default | Notes |
|---|---|---|---|
| `type` | yes | — | `"STAY"` |
| `location_anchor` | yes | — | see `StaysSearchAnchor` below |
| `check_in_date` | yes | — | `YYYY-MM-DD` |
| `check_out_date` | yes | — | `YYYY-MM-DD` |
| `rooms` | no | `1` | 1–8 |
| `guests` | no | `1` | number of adult guests |
| `radius_km` | no | `50` | search radius in km around the anchor |
**`StaysSearchAnchor` object:**
| Field | Required | Description |
|---|---|---|
| `value` | yes | Reference point text, e.g. `"Milano"`, `"London center"`, `"LHR"` |
| `type` | yes | One of: `airport`, `city`, `city_center`, `region`, `address`, `place`, `others` |
Anchor type guide:
- `airport` — 3-letter IATA code (e.g. `"LHR"`)
- `city` — city name (e.g. `"Abu Dhabi"`)
- `city_center` — city centre / downtown (e.g. `"London center"`)
- `region` — neighbourhood / district (e.g. `"Manhattan"`)
- `address` — street address (e.g. `"Via G.B. Pioda 10"`)
- `place` — landmark / point of interest (e.g. `"Parque Edgar Sampaio Fontes"`)
- `others` — anything else
Response (200):
```json
{
"search_id": "def456",
"total_offers": 15,
"best_offers": [ /* top 3 offers */ ]
}
```
| Field | Description |
|---|---|
| `search_id` | Identifier for this search — use in all subsequent calls |
| `total_offers` | Total number of offers matching the search |
| `best_offers` | The 3 best offers |
### List offers
```
GET /searches/{search_id}/offers?limit=20&offset=0
Authorization: Bearer {access_token}
```
| Parameter | Required | Default | Description |
|---|---|---|---|
| `search_id` | yes (path) | — | `search_id` from `POST /searches` |
| `limit` | no (query) | `20` | Max offers to return (1–200) |
| `offset` | no (query) | `0` | Offset for pagination |
Response (200):
```json
{
"total_offers": 42,
"offers": [ /* paginated list of offer summaries */ ]
}
```
| Field | Description |
|---|---|
| `total_offers` | Total number of offers for this search |
| `offers` | Paginated slice of offer summaries |
### Get single offer
```
GET /searches/{search_id}/offers/{offer_id}
Authorization: Bearer {access_token}
```
Response (200): a single offer summary object.
### Offer pricing (flights)
```
GET /searches/{search_id}/offers/{offer_id}/pricing
Authorization: Bearer {access_token}
```
| Path parameter | Description |
|---|---|
| `search_id` | `search_id` from `POST /searches` |
| `offer_id` | Offer ID from `best_offers` or the paginated offers list |
Response (200): a flat offer summary dict with full pricing details (final price, segments, airlines, etc.).
Error (410): returns `{ "detail": "EXPIRED_OFFER" }` if the offer is no longer available. When this happens, inform the user and re-run the search.
### Hotel rates
```
GET /searches/{search_id}/offers/{offer_id}/rates
Authorization: Bearer {access_token}
```
| Path parameter | Description |
|---|---|
| `search_id` | `search_id` from `POST /searches` |
| `offer_id` | Hotel offer ID from `best_offers` or the paginated offers list |
Response (200): a list of room objects, each containing room details (name, description, size, max occupancy, photos, beds) and nested `rates` array with pricing, payment type, and cancellation conditions.
### Rate pricing (stays)
```
GET /searches/{search_id}/offers/{offer_id}/rates/{rate_id}/pricing
Authorization: Bearer {access_token}
```
| Path parameter | Description |
|---|---|
| `search_id` | `search_id` from `POST /searches` |
| `offer_id` | Hotel offer ID |
| `rate_id` | Rate ID from the rates list |
Response (200): a stay summary with full pricing details (total amount, currency, accommodation info, room breakdown).
Error (410): returns `{ "detail": "EXPIRED_OFFER" }` if the rate is no longer available. When this happens, inform the user and re-run the search.
### Hotel photo gallery
```
GET /searches/{search_id}/offers/{offer_id}/photos
```
No authentication required. Returns an interactive HTML photo gallery for the hotel.
| Path parameter | Description |
|---|---|
| `search_id` | `search_id` from `POST /searches` |
| `offer_id` | Hotel offer ID |
**How to present to the user:** Build the full gallery URL and share it as a clickable link:
```
https://api.travai.tech/searches/{search_id}/offers/{offer_id}/photos
```
Do **not** share individual photo URLs from the API response — they are CDN-signed and will not load in the browser. Always use the gallery link instead.
The gallery features keyboard navigation (arrow keys), click-to-advance, and a thumbnail strip.
Individual photos can also be accessed at `/searches/{search_id}/offers/{offer_id}/photos/{index}` (0-based) if needed for embedding.
### List tokens
```
GET /tokens
Authorization: Bearer {access_token}
```
Response (200): a flat list of supported crypto tokens. Each item represents one asset on one blockchain.
```json
[
{ "assetId": "nep141:wrap.near", "blockchain": "NEAR", "symbol": "NEAR" },
{ "assetId": "nep141:usdc.near", "blockchain": "NEAR", "symbol": "USDC" },
{ "assetId": "eth:0xabc...", "blockchain": "Ethereum", "symbol": "USDC" }
]
```
| Field | Description |
|---|---|
| `assetId` | Token identifier — use as `origin_asset` in a CRYPTO payment |
| `blockchain` | Blockchain network the token lives on |
| `symbol` | Token symbol (e.g. `"NEAR"`, `"USDC"`) |
**Token selection guidance:**
- **Group by `blockchain`** when presenting to the user
- Prefer **stablecoins** (USDC, USDT) for payment unless the user requests otherwise
- When user asks for a specific token/network, match by `symbol` + `blockchain`
- Always confirm the exact `assetId` before proceeding
After the user picks a token, ask for their wallet address on that token's `blockchain` to use as `refund_to`.
### Initiate payment
```
POST /payments
Authorization: Bearer {access_token}
Content-Type: application/json
```
**Payment methods:** `CRYPTO` or `CARD`. Use the `method` field (not `type`).
#### CRYPTO payment
```json
{
"method": "CRYPTO",
"origin_asset": "{assetId from GET /tokens}",
"refund_to": "{user wallet address}",
"offers": [ /* see offer object below */ ]
}
```
| Field | Required | Description |
|---|---|---|
| `method` | yes | `"CRYPTO"` |
| `origin_asset` | yes | `assetId` of the token selected by the user from `GET /tokens` |
| `refund_to` | yes | User's wallet address on the selected token's network — refunds go here in the same currency |
| `offers` | yes | Selected offers to pay for |
Response (200):
```json
{
"payment_id": 123,
"itinerary_id": 456,
"deposit_address": "travai.near",
"amount_in": "1000000",
"amount_in_formatted": "1.00",
"total_amount": 100,
"currency": "USD"
}
```
| Field | Description |
|---|---|
| `payment_id` | Payment identifier (use to check status via `GET /payments/{payment_id}`) |
| `itinerary_id` | Itinerary identifier (use to get confirmations via `GET /itineraries/{itinerary_id}/offers`) |
| `deposit_address` | Address to send tokens to |
| `amount_in_formatted` | **Exact** amount of tokens to send — must match exactly, do not round |
| `amount_in` | Raw token amount (integer string) |
| `total_amount` | Fiat equivalent in smallest unit (cents) |
| `currency` | ISO currency code (e.g. `"USD"`) |
#### CARD payment
```json
{
"method": "CARD",
"success_url": "https://app.travai.tech/?payment=success",
"cancel_url": "https://app.travai.tech/?payment=cancel",
"offers": [ /* see offer object below */ ]
}
```
| Field | Required | Description |
|---|---|---|
| `method` | yes | `"CARD"` |
| `success_url` | yes | Always `"https://app.travai.tech/?payment=success"` |
| `cancel_url` | yes | Always `"https://app.travai.tech/?payment=cancel"` |
| `offers` | yes | Selected offers to pay for |
Response (200):
```json
{
"payment_id": 123,
"itinerary_id": 456,
"payment_url": "https://checkout.stripe.com/...",
"total_amount": 100,
"currency": "USD"
}
```
| Field | Description |
|---|---|
| `payment_id` | Payment identifier (use to check status via `GET /payments/{payment_id}`) |
| `itinerary_id` | Itinerary identifier (use to get confirmations via `GET /itineraries/{itinerary_id}/offers`) |
| `payment_url` | URL for the user to complete card payment |
| `total_amount` | Amount in smallest currency unit (cents) |
| `currency` | ISO currency code (e.g. `"USD"`) |
### Get payment status
```
GET /payments/{payment_id}
Authorization: Bearer {access_token}
```
| Path parameter | Description |
|---|---|
| `payment_id` | Payment ID from the `POST /payments` response |
Response (200): full payment record including `status`, `fiat_amount`, `fiat_currency`, `token_amount`, `token_asset`, `created_at`, `updated_at`.
Payment statuses: `PENDING`, `PENDING_DEPOSIT`, `INCOMPLETE_DEPOSIT`, `KNOWN_DEPOSIT_TX`, `PROCESSING`, `SUCCESS`, `REFUNDED`, `FAILED`, `ERROR`.
### Get itinerary offers
```
GET /itineraries/{itinerary_id}/offers
Authorization: Bearer {access_token}
```
| Path parameter | Description |
|---|---|
| `itinerary_id` | Itinerary ID from the `POST /payments` response |
Response (200): list of itinerary offer items, each with `itinerary_offer_id`, `type`, `provider`, `price`, `currency`, `status`, `booking_summary`, `created_at`.
### Offer object (inside `offers` array in payment request)
```json
{
"type": "FLIGHT",
"search_id": "abc123",
"offer_id": "offer_xyz",
"customers": [
{
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"date_of_birth": "1989-04-23",
"gender": "FEMALE",
"nationality": "UA",
"country_code": "380",
"phone_number": "632816433",
"title": "MS"
}
]
}
```
| Field | Required | Description |
|---|---|---|
| `type` | yes | `"FLIGHT"` or `"STAY"` |
| `search_id` | yes | `search_id` from the search step |
| `offer_id` | yes (FLIGHT) | Offer ID from the search results |
| `rate_id` | yes (STAY) | Rate ID from the hotel rates step |
| `customers` | yes | Traveller details (see below) |
### Customer object (inside offer `customers` array)
| Field | Required | Example |
|---|---|---|
| `first_name` | yes | `Jane` |
| `last_name` | yes | `Doe` |
| `title` | yes | `MR`, `MRS`, `MS`, `DR` |
| `gender` | yes | `MALE` or `FEMALE` |
| `date_of_birth` | yes | `1989-04-23` (YYYY-MM-DD) |
| `nationality` | yes | `UA`, `GB`, `US` (ISO 3166-1 alpha-2) |
| `country_code` | yes | `380` (phone country code, no `+`) |
| `phone_number` | yes | `632816433` (without country code) |
| `email` | yes | `jane@example.com` |
**IMPORTANT:** `first_name`, `last_name`, and `date_of_birth` must exactly match the traveller's international passport. Always remind the user of this before collecting their details.
---
## Workflow checklists
### Flight booking checklist
1. **Authenticate** — sign in, sign up, or accept a pasted token
2. **Gather details** — origin, destination, dates, passengers
3. **Search** — `POST /searches` with `type: "FLIGHT"`
4. **Present best offers** — show price, airlines, stops, duration
5. **Filter if needed** — e.g. direct flights only (1 segment per slice)
6. **Confirm pricing** — `GET /searches/{search_id}/offers/{offer_id}/pricing`
7. **Handle expiry** — if 410, re-run search and start from step 3
8. **Collect traveller details** — warn about passport matching first
9. **Choose payment method** — crypto or card
10. **If crypto** — `GET /tokens`, present grouped by blockchain (prefer stablecoins), collect wallet address for refund
11. **Create payment** — `POST /payments` with `method` field
12. **Present payment instructions:**
- Crypto: exact `amount_in_formatted` + token + `deposit_address` + refund wallet
- Card: full untruncated `payment_url`
13. **Check status if asked** — `GET /payments/{payment_id}`
### Stay booking checklist
1. **Authenticate** — sign in, sign up, or accept a pasted token
2. **Gather details** — destination, check-in/out dates, rooms, guests
3. **Search** — `POST /searches` with `type: "STAY"`
4. **Present best offers** — show property name, price per night, rating, distance. Share the photo gallery link: `https://api.travai.tech/searches/{search_id}/offers/{offer_id}/photos` — do NOT share raw photo URLs
5. **Get rates** — immediately `GET /searches/{search_id}/offers/{offer_id}/rates` when user picks a hotel
6. **Present rates** — room type, price, amenities, cancellation policy
7. **Confirm pricing** — immediately `GET .../rates/{rate_id}/pricing` when user picks a rate
8. **Handle expiry** — if 410, re-run search and start from step 3
9. **Collect traveller details** — warn about passport matching first
10. **Choose payment method** — crypto or card
11. **Create payment** — include `rate_id` in offer object, use `method` field
12. **Present payment instructions** (same as flight)
13. **Check status if asked** — `GET /payments/{payment_id}`
---
## Nearby Airports Database
Many cities are served by multiple airports. Always check this mapping before searching — run parallel searches for all relevant airports and compare results.
If a city is not listed below but the user mentions a country or region, use your knowledge of IATA codes to identify all relevant airports.
### Europe — Western
| City / Country | Airports (IATA) |
|---|---|
| London / UK | LHR, LGW, STN, LTN, LCY |
| Manchester / UK | MAN, LPL (Liverpool, 1h) |
| Edinburgh / UK | EDI, GLA (Glasgow, 1h) |
| Paris / France | CDG, ORY, BVA |
| Nice / France | NCE |
| Lyon / France | LYS |
| Amsterdam / Netherlands | AMS, EIN (Eindhoven), RTM (Rotterdam) |
| Brussels / Belgium | BRU, CRL (Charleroi) |
| Berlin / Germany | BER |
| Frankfurt / Germany | FRA, HHN (Hahn) |
| Munich / Germany | MUC, NUE (Nuremberg, nearby) |
| Düsseldorf / Germany | DUS, CGN (Cologne, 1h), DTM (Dortmund) |
| Hamburg / Germany | HAM |
| Zurich / Switzerland | ZRH, BSL (Basel), BRN (Bern) |
| Geneva / Switzerland | GVA |
| Vienna / Austria | VIE, BTS (Bratislava, 80km) |
| Dublin / Ireland | DUB, SNN (Shannon), ORK (Cork) |
### Europe — Southern
| City / Country | Airports (IATA) |
|---|---|
| Milan / Italy | MXP, LIN, BGY (Bergamo) |
| Rome / Italy | FCO, CIA (Ciampino) |
| Naples / Italy | NAP |
| Venice / Italy | VCE, TSF (Treviso) |
| Florence / Italy | FLR, PSA (Pisa, 1h) |
| Bologna / Italy | BLQ |
| Catania / Sicily | CTA, PMO (Palermo) |
| Madrid / Spain | MAD |
| Barcelona / Spain | BCN, GRO (Girona), REU (Reus) |
| Malaga / Spain | AGP |
| Valencia / Spain | VLC |
| Seville / Spain | SVQ |
| Alicante / Spain | ALC |
| Palma de Mallorca / Spain | PMI |
| Lisbon / Portugal | LIS |
| Porto / Portugal | OPO |
| Athens / Greece | ATH |
| Thessaloniki / Greece | SKG |
| Istanbul / Turkey | IST, SAW (Sabiha Gökçen) |
| Antalya / Turkey | AYT |
### Europe — Northern
| City / Country | Airports (IATA) |
|---|---|
| Stockholm / Sweden | ARN, BMA, NYO (Skavsta), VST (Västerås) |
| Copenhagen / Denmark | CPH, BLL (Billund), AAR (Aarhus) |
| Oslo / Norway | OSL, TRF (Torp), RYG (Rygge) |
| Helsinki / Finland | HEL, TMP (Tampere) |
| Reykjavik / Iceland | KEF, RKV |
### Europe — Eastern & Central
| City / Country | Airports (IATA) |
|---|---|
| Prague / Czech Republic | PRG |
| Warsaw / Poland | WAW, WMI (Modlin) |
| Krakow / Poland | KRK |
| Gdansk / Poland | GDN |
| Wroclaw / Poland | WRO |
| Budapest / Hungary | BUD |
| Bucharest / Romania | OTP, BBU (Băneasa) |
| Sofia / Bulgaria | SOF |
| Kyiv / Ukraine | KBP, IEV (Zhuliany) |
| Belgrade / Serbia | BEG |
| Zagreb / Croatia | ZAG |
| Split / Croatia | SPU |
| Dubrovnik / Croatia | DBV |
| Riga / Latvia | RIX |
| Vilnius / Lithuania | VNO |
| Tallinn / Estonia | TLL |
| Bratislava / Slovakia | BTS, VIE (Vienna, 60km) |
### Middle East
| City / Country | Airports (IATA) |
|---|---|
| Dubai / UAE | DXB, DWC (Al Maktoum) |
| Abu Dhabi / UAE | AUH |
| Doha / Qatar | DOH |
| Riyadh / Saudi Arabia | RUH |
| Jeddah / Saudi Arabia | JED |
| Muscat / Oman | MCT |
| Bahrain | BAH |
| Kuwait | KWI |
| Amman / Jordan | AMM |
| Tel Aviv / Israel | TLV |
| Beirut / Lebanon | BEY |
| Cairo / Egypt | CAI |
| Sharm El Sheikh / Egypt | SSH |
| Hurghada / Egypt | HRG |
### Asia — East
| City / Country | Airports (IATA) |
|---|---|
| Tokyo / Japan | NRT (Narita), HND (Haneda) |
| Osaka / Japan | KIX (Kansai), ITM (Itami) |
| Seoul / South Korea | ICN (Incheon), GMP (Gimpo) |
| Beijing / China | PEK (Capital), PKX (Daxing) |
| Shanghai / China | PVG (Pudong), SHA (Hongqiao) |
| Guangzhou / China | CAN |
| Shenzhen / China | SZX |
| Hong Kong | HKG |
| Taipei / Taiwan | TPE (Taoyuan), TSA (Songshan) |
### Asia — Southeast
| City / Country | Airports (IATA) |
|---|---|
| Bangkok / Thailand | BKK (Suvarnabhumi), DMK (Don Mueang) |
| Phuket / Thailand | HKT |
| Chiang Mai / Thailand | CNX |
| Singapore | SIN |
| Kuala Lumpur / Malaysia | KUL, SZB (Sultan Abdul Aziz Shah) |
| Jakarta / Indonesia | CGK (Soekarno-Hatta) |
| Bali / Indonesia | DPS |
| Manila / Philippines | MNL |
| Ho Chi Minh City / Vietnam | SGN |
| Hanoi / Vietnam | HAN |
| Phnom Penh / Cambodia | PNH |
| Siem Reap / Cambodia | REP |
| Yangon / Myanmar | RGN |
### Asia — South & Central
| City / Country | Airports (IATA) |
|---|---|
| Delhi / India | DEL |
| Mumbai / India | BOM |
| Bangalore / India | BLR |
| Colombo / Sri Lanka | CMB |
| Maldives | MLE |
| Kathmandu / Nepal | KTM |
| Almaty / Kazakhstan | ALA |
| Tbilisi / Georgia | TBS |
| Yerevan / Armenia | EVN |
| Baku / Azerbaijan | GYD |
| Tashkent / Uzbekistan | TAS |
### Africa
| City / Country | Airports (IATA) |
|---|---|
| Johannesburg / South Africa | JNB, HLA (Lanseria) |
| Cape Town / South Africa | CPT |
| Nairobi / Kenya | NBO |
| Addis Ababa / Ethiopia | ADD |
| Casablanca / Morocco | CMN |
| Marrakech / Morocco | RAK |
| Tunis / Tunisia | TUN |
| Lagos / Nigeria | LOS |
| Accra / Ghana | ACC |
| Dar es Salaam / Tanzania | DAR |
| Zanzibar / Tanzania | ZNZ |
| Mauritius | MRU |
### North America
| City / Country | Airports (IATA) |
|---|---|
| New York / USA | JFK, EWR (Newark), LGA (LaGuardia) |
| Los Angeles / USA | LAX, BUR (Burbank), LGB (Long Beach), ONT, SNA (Orange County) |
| San Francisco / USA | SFO, OAK (Oakland), SJC (San Jose) |
| Chicago / USA | ORD (O'Hare), MDW (Midway) |
| Miami / USA | MIA, FLL (Fort Lauderdale), PBI (West Palm Beach) |
| Washington DC / USA | IAD (Dulles), DCA (Reagan), BWI (Baltimore) |
| Dallas / USA | DFW, DAL (Love Field) |
| Houston / USA | IAH, HOU (Hobby) |
| Boston / USA | BOS |
| Seattle / USA | SEA |
| Atlanta / USA | ATL |
| Denver / USA | DEN |
| Las Vegas / USA | LAS |
| Honolulu / USA | HNL |
| Toronto / Canada | YYZ, YTZ (Billy Bishop) |
| Montreal / Canada | YUL |
| Vancouver / Canada | YVR |
| Mexico City / Mexico | MEX, NLU (Felipe Ángeles) |
| Cancun / Mexico | CUN |
### South America
| City / Country | Airports (IATA) |
|---|---|
| São Paulo / Brazil | GRU (Guarulhos), CGH (Congonhas), VCP (Campinas) |
| Rio de Janeiro / Brazil | GIG (Galeão), SDU (Santos Dumont) |
| Buenos Aires / Argentina | EZE (Ezeiza), AEP (Aeroparque) |
| Santiago / Chile | SCL |
| Lima / Peru | LIM |
| Bogota / Colombia | BOG |
| Medellin / Colombia | MDE, EOH (Olaya Herrera) |
| Cartagena / Colombia | CTG |
### Oceania
| City / Country | Airports (IATA) |
|---|---|
| Sydney / Australia | SYD |
| Melbourne / Australia | MEL, AVV (Avalon) |
| Brisbane / Australia | BNE |
| Perth / Australia | PER |
| Auckland / New Zealand | AKL |
| Queenstown / New Zealand | ZQN |
| Fiji | NAN |
---
## Smart Flight Search Strategy
This is the core of intelligent search. Always follow this protocol.
### Step 1 — Identify all relevant airports
Before searching, check the Nearby Airports Database for both origin and destination. If the city has multiple airports, plan parallel searches.
**Example:** User says "London to Milan" — search LHR->MXP, LHR->LIN, LHR->BGY, LGW->MXP, LGW->LIN, LGW->BGY, STN->MXP, STN->BGY in parallel. Surface cheapest overall.
### Step 2 — Run parallel searches
Always launch multiple searches simultaneously using parallel tool calls:
- All relevant airport combinations
- If flexible dates: target date +/-1 day (+/-2 days if user explicitly flexible)
**Flexible date matrix example** (+/-1 day, 1 origin, 1 destination = 3 searches):
- departure_date - 1 day
- departure_date (requested)
- departure_date + 1 day
If user says they are flexible or "around that date", go +/-2 days (5 searches).
### Step 3 — Analyse and surface best options
After collecting all results:
1. Find the **cheapest overall** across all airport/date combinations — highlight it
2. Find the **fastest** (fewest stops, shortest duration) — highlight separately
3. Find the **best value** (price + duration balanced)
4. Flag anything unusual: next-day arrival, very long layover (>4h), overnight layover, short layover (<60min — warn about connection risk)
### Step 4 — Auto-fallback if 0 results
If a search returns 0 offers:
1. Immediately try adjacent dates (+/-1 day) without asking the user
2. Try nearby airports automatically
3. Only if all fallbacks return 0, inform the user and suggest alternatives
### Step 5 — Proactively ask about flexibility
After showing results, if prices are high (>$300 for short-haul, >$700 for medium-haul), proactively say:
> "Prices on this date are on the higher side. Would you like me to check +/-1-2 days to find a cheaper option?"
---
## Human Travel Agent Behaviours
Act like an experienced travel agent, not a search engine.
### Always flag these automatically (without being asked):
- **Next-day arrival**: "Note: this flight arrives on April 18th, not April 17th — overnight layover in Copenhagen."
- **Short layover (<60 min)**: "Warning: 45-min connection in Frankfurt is very tight — any delay on the first leg could cause a miss."
- **Baggage not included**: "No bags included — adding checked baggage will increase the price."
- **Non-refundable**: "This ticket is non-refundable and non-changeable."
- **Instant payment required**: "This fare requires immediate payment — price may not be held."
- **Train might be better**: For routes under 500-600 km where the total door-to-door time by train competes with flying (e.g. Paris-Brussels, Berlin-Amsterdam, London-Paris), proactively mention: "For this route, a train might be faster and more convenient door-to-door — though I can only book flights here."
### Presentation style:
- Lead with the **cheapest** option and mark it clearly
- Show a **"best overall"** pick that balances price, duration, and convenience — with a brief reason why
- When showing a connecting flight with a bad layover city, note it naturally: "This goes via Oslo — a bit of a detour, but it's the cheapest same-day option."
- Use conversational language alongside tables. E.g.: "I found 174 flights — here are the 3 worth looking at:"
- If the user's requested date has no good options, proactively show the best option on adjacent dates even if not asked
### When searching multi-city / complex itineraries:
- Break the trip into segments and search each independently in parallel
- Consider departure airports for return legs — e.g. if user ends in Maastricht, check BRU, EIN, AMS for return
- Always note which airport the return departs from — it may require travel time
### Price intelligence:
- If the same route shows wildly different prices across dates (e.g. Friday vs Tuesday), proactively mention: "Tuesday departures are significantly cheaper on this route — worth considering if you have flexibility."
- If baggage fees would make a "cheap" fare more expensive than a higher-priced fare with bags included, flag it: "Adding a bag to the $89 fare would bring it to ~$140 — the $125 Lufthansa fare with bags included may actually be better value."
---
## Flight Results Display Format
### 1. Header
Always start with a header containing route, dates, passengers, and total offers found.
**Template:**
```
## [City] [IATA] -> [City] [IATA] | [Departure date] - [Return date] | [N] adults
Found [X] offers. Here are the best:
```
### 2. Result Groups
All results are divided into three groups in strict order:
**Group 1 — Cheapest**
Header: `### Cheapest`
- 3-5 offers with lowest price, sorted ascending
- If same price — pick shortest total travel time
**Group 2 — Fastest**
Header: `### Fastest`
- 3 offers with shortest total duration (outbound + return)
- Even if already in "cheapest" — show here separately
- Sorted by duration ascending
**Group 3 — Other options**
Header: `### Other options`
- 2-3 interesting offers not in groups above
- E.g. different airline, different departure airport, good price/time balance
### 3. Table Format
**Round trip:**
| Route out | Departure -> Arrival (duration) | Class | Route back | Departure -> Arrival (duration) | Class | Price |
|---|---|---|---|---|---|---|
**One way:**
| Route | Departure -> Arrival (duration) | Class | Price |
|---|---|---|---|
**Column rules:**
**Route (out / back):**
- Format: City IATA -> City IATA (with city name next to each code)
- Include layovers in the chain
- If direct — mark "(direct)"
- Examples: `Bangkok BKK -> Kuala Lumpur KUL -> Singapore SIN`, `Amsterdam AMS -> Barcelona BCN (direct)`
**Departure -> Arrival (duration):**
- Date format: dd/mm
- Time format: hh:mm (local time)
- Duration: Xh Ym (segment duration only, not total)
- If arrival next day — add `+1` after arrival time and include arrival date
- Normal example: `06/10 06:00 -> 11:25 (4h 25m)`
- Next day example: `06/10 23:00 -> 06/11 03:30 +1 (4h 30m)`
**Class:** Economy / Business / First
**Price:**
- With currency sign: $330.60 or EUR 298
- Lowest price in group — bold
- "Best pick" offer — mark as best pick before price
### 4. Caption Under Table
After each table — one italic line with key details.
**Key info to include per offer:**
- Airline name
- Baggage included / not included
- Refundable / non-refundable
- Changes allowed / not allowed
- Short connection warning (under 60 min)
- Long layover warning (over 6h)
- Direct flight indicator
- Layover city
**Caption template:**
```
*[Airline] · baggage included · non-refundable · ...*
```
**Next-day arrival — always use `+1`:**
- `+1` goes right after arrival time in table cell
- Always include arrival date next to it
- Example: `05/11 23:25 -> 05/12 06:25 +1`
### 5. Summary Block
After all tables — a summary block with:
**5.1 Cheapest**
```
Cheapest: [Airline] — [price]. [pros]. But: [cons].
```
**5.2 Best pick**
A reasoned recommendation — not always the cheapest. Consider: direct vs layover, refund, baggage, price difference.
```
Best pick: [Airline], [route] — [price] ([difference] more). [conditions]. [One-sentence rationale].
```
**5.3 Warnings (if any)**
Each warning — separate line. Must mention:
- Next-day arrival
- Short connection
- Long layover
- Baggage not included in cheap fares
- Departure from different airport
**5.4 Call to action**
Last line — always offer to proceed to booking:
```
Want to book? Name your preferred option and I'll request exact pricing and details.
```
### 6. Language
Always respond in the language the user is writing in. The structure stays the same regardless of language.
---
## Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 422 `method` field required | Sent `type` instead of `method` in payment request | Use `"method": "CRYPTO"` or `"method": "CARD"` |
| `Search ... not found` | Search expired or invalid `search_id` | Re-run `POST /searches` and use fresh `search_id` |
| 404 `Offer ... not found in search` | Offer ID from a different/old search | Re-run search and use `offer_id` values from the new results |
| 410 `EXPIRED_OFFER` | Offer or rate is no longer available | Re-run search, pick a fresh offer, then retry |
| 422 on signup | Sent `name` instead of `first_name`/`last_name` | Use separate `first_name` and `last_name` fields |
| Unsupported token | Token/chain mismatch | Call `GET /tokens` and pick a valid `assetId` |
| Redirect loop on POST | Trailing-slash routing issue | Ensure POST goes to `/payments` (no trailing slash) |
---
## Changes and cancellations
Booking changes and cancellations **cannot** be done via the API. The user must contact support directly:
- **Telegram:** https://t.me/travaiofficial
- **Email:** support@travai.tech
Response time: within 24 hours.
When a user asks to change or cancel a booking, do not attempt any API calls — instead, direct them to one of the support channels above.
---
## Support
For any technical issues, questions, or problems with the API:
- **Telegram:** https://t.me/travaiofficial
- **Email:** support@travai.tech
We will reply within 24 hours.
---
## Tips
- Always authenticate before making API calls
- Always check the Nearby Airports Database before searching — run parallel searches for all relevant airports
- If the user gives flexible dates or "around that time", search +/-2 days automatically
- If a search returns 0 results, auto-retry with adjacent dates and nearby airports before telling the user
- Present flight offers sorted by price, but always highlight a "best overall" pick with a brief reason
- For round trips, always include both departure and return dates
- For stays, present offers highlighting price per night, rating, and proximity to the city centre
- When the user's intent is ambiguous, ask whether they need a flight, a stay, or both
- Always flag next-day arrivals, overnight layovers, short connections, and missing baggage proactively
- For short-haul routes (<600km), mention the train option even though TravAI can't book it
- For crypto payments, present the deposit info clearly and warn not to round amounts or substitute tokens
don't have the plugin yet? install it then click "run inline in claude" again.