Caching & monitoring
Error handling and empty-result states: Error handling. Platform HTTP errors and retries: Error Handling.
Data freshness
Ticket results are cached by the API for up to 1 hour. The cache key is the full query tuple: (origin, destination, date, passengers). Any change in passengers or date produces a separate cache entry and a fresh API call.
| Endpoint | Recommended TTL | Cache key | Notes |
|---|---|---|---|
GET /tickets/search | 1 hour max | tickets:{origin}:{dest}:{date}:{passengers} | Matches API cache lifetime — longer TTLs risk showing stale prices |
Disclosure: Always present prices as indicative market snapshots. State clearly in passenger-facing UI that prices may change between search and booking (for example: "Prices may change at booking. Confirm fare on the airline's site.").
Debounce
Ticket search UIs typically fire on every airport or date selection change. Debounce inputs at 300–500 ms before issuing a request to avoid flooding the API with partial queries while the user is typing.
import os
import time
import requests
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
_cache: dict[str, tuple[dict, float]] = {}
TTL = 3600 # 1 hour — matches API cache lifetime
def search_tickets_cached(
origin: str,
destination: str,
date: str,
passengers: int = 1,
) -> dict:
"""
Cache-wrapped ticket search. TTL 1 hour, keyed on (origin, dest, date, passengers).
Raises ValueError on 422. Returns full response dict including count and flights.
"""
cache_key = f"tickets:{origin}:{destination}:{date}:{passengers}"
cached = _cache.get(cache_key)
if cached and time.time() < cached[1]:
return cached[0]
r = requests.get(
f"{BASE}/tickets/search",
headers=HEADERS,
params={
"origin": origin,
"destination": destination,
"date": date,
"passengers": passengers,
},
timeout=(10, 15),
)
if r.status_code == 422:
detail = r.json().get("detail", "validation error")
raise ValueError(f"422 Unprocessable: {detail}")
r.raise_for_status()
data = r.json()
_cache[cache_key] = (data, time.time() + TTL)
return data
if __name__ == "__main__":
result = search_tickets_cached("JFK", "LAX", date="2026-09-15", passengers=1)
flights = result.get("flights", [])
print(f"Found {result.get('count', 0)} option(s).")
if flights:
cheapest = flights[0]
duration = cheapest.get("total_duration_min", 0)
print(
f"Cheapest: ${cheapest.get('price_usd', 0):.2f} · "
f"{duration // 60}h {duration % 60:02d}m"
)
print("Prices may change at booking.")
result2 = search_tickets_cached("JFK", "LAX", date="2026-09-15", passengers=1)
print("Cache hit:", result2 is result)For production, replace the dict-based cache with Redis or Memcached so the cache survives restarts and is shared across instances.
Rate limits
| Header | Value |
|---|---|
X-RateLimit-Requests-Limit | Your plan's monthly request quota |
X-RateLimit-Requests-Remaining | Requests left this month |
X-RateLimit-Requests-Reset | Seconds until the quota resets |
With a 1-hour TTL per (origin, destination, date, passengers) tuple, each unique search consumes at most one API call per hour. For high-traffic flows, pre-populate the cache with the most common routes during off-peak periods.
Monitoring
Log on every ticket search:
origin,destination,date,passengersstatus_codeelapsed_mscount(from response — useful for tracking no-availability rates)cache_hit(boolean)quota_remaining(fromX-RateLimit-Requests-Remaining)
Alert on:
count: 0rate spike — may indicate a broken date range or query parameter issuestatus = 429more than twice in a 5-minute window — increase TTL or throttle concurrent searcheselapsed_ms > 5000repeatedly — consider a shorter timeout with a "try again" fallback messagequota_remaining < 200— approaching monthly limit; extend TTLs or reduce unique search volume
Related: Flight Tickets · Airport Search · Error handling · Error Handling