Files
Flights/api_docs/route_explorer_api.md
T
Trey T b403bfd970 Add route-explorer.com integration: connection finder + departures board
- RouteExplorerClient: anonymous HMAC token (route-explorer.com/api/token,
  IP rate-limited 10/min), POST /api/flight-search with X-API-Token; auto
  retry on 401/403 token rotation. Wraps the SuperJSON {json:{...}} envelope
  for the upstream tRPC endpoints.
- RouteExplorerModels: Codable types for /route, /departures responses
  (RouteConnection, RouteFlight, cabins, appendix). Custom ISO-8601
  decoder for the dateTime-with-offset timestamps. Bridge helper
  RouteFlight.toFlightSchedule(...) so route-explorer legs reuse the
  existing FlightLoadDetailView and AirlineLoadService flow for
  supported carriers (UA/AA/NK/KE/B6/AS/EK/XE).
- RoutePlannerView: feature (a) — direct + multi-stop A→B routing via
  /route with maxStops 0/1/2, sortBy departure_time/duration, optional
  interline-only filter. Renders one ConnectionRow per itinerary with
  chained legs and layover indicators.
- WhereToGoView: feature (b) — "where can I go" departures board for an
  airport over a 2/4/6/12/24h window. Capacity pills (F/J/W/Y), color-
  coded countdown, cross-midnight rollover. Tap any leg → load detail.
- IATAAirportPicker: lightweight local-only picker against
  AirportDatabase (no flightconnections roundtrip needed since
  route-explorer keys on IATA, not FC IDs).
- ContentView: two new entry-point cards (Find Connections, Where can I
  go?) above the favorites list.
- api_docs/route_explorer_api.md + captures: full endpoint reference and
  representative response samples (DFW→LAS direct, DFW→KOA 1-stop,
  LBB→KOA 2-stop, AA2178 schedule, DFW departures).

No tests yet — project has no test target and adding TDD would require
scaffolding XCTest first. Worth backfilling tests for the date decoder,
layover math, and toFlightSchedule bridge using the saved fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:23:17 -05:00

7.8 KiB

Route Explorer API surface (route-explorer.com)

Captured 2026-04-25 from https://route-explorer.com (StaffTraveler's public web app).

Architecture

Browser ──GET /api/token──> Vercel function (returns HMAC token)
Browser ──POST /api/flight-search { X-API-Token } ──> Vercel function ──> upstream tRPC
Browser ──GET /api/weather { X-API-Token } ────────> Vercel function ──> open-meteo
Browser ──GET *.public.blob.vercel-storage.com/data/routes/{IATA}.json ──> static CDN, no auth

The browser never calls api.stafftraveler.com directly despite it being in the page CSP. Everything routes through route-explorer.com/api/* Vercel functions, which validate X-API-Token and proxy upstream. Upstream uses tRPC with SuperJSON envelopes ({ json: {...} }).

Auth: /api/token

GET https://route-explorer.com/api/token

Response:

{
  "token": "1777091466.01c0355b2bbab0cacfd37cf3ebb9ce1f.f3fcb79c6f60c7cfaa1750000c133a2c7add44973329a9d74429a1622ab4cdc2",
  "countryCode": "US"
}
  • Format: {unixSeconds}.{16-byte-id-hex}.{32-byte-hmac-hex}
  • IP rate-limited: X-RateLimit-Limit: 10 per window (X-RateLimit-Reset returns next window unix time).
  • No login, no cookie required. credentials: include is harmless.
  • TTL not measured; tokens issued seconds apart all worked. Refresh per session.

Pass as X-API-Token: <token> to /api/flight-search and /api/weather.

/api/flight-search (POST)

POST /api/flight-search
Content-Type: application/json
X-API-Token: <token>

{ "endpoint": "/<one of allowed>", "body": { "json": { ... params ... } } }

Allowed values for endpoint: /route, /route-batch, /flight, /departures, /schedule. Anything else returns 400 with {"error":"Invalid endpoint '...'. Allowed: ..."} — proxy enforces an allowlist.

Responses are tRPC SuperJSON: { "json": { ... } }. Errors include a code/status and a data.issues[] array straight from a Zod schema, which leaks the parameter names.

/schedule

Required: carrierCode (str), flightNumber (num), startDate (YYYY-MM-DD), endDate (YYYY-MM-DD). Optional: limit, includeAppendix.

Returns one row per operating day in the range, with full per-cabin seat counts and equipment.

Sample response: route_explorer_captures/schedule_AA2178.json

/route — also does multi-leg connection finding

Required: departureAirportIata, arrivalAirportIata, departureDates (string[]).

Optional (observed in real Connections-tab traffic):

  • maxStops0 for direct only, 1 or 2 for multi-leg search. Server returns already-joined itineraries; no client-side join needed.
  • sortBy"departure_time" | "duration" (and likely "arrival_time", "score", "stops")
  • includeInterline — boolean. When true, restricts/highlights carriers with interline non-rev agreements.
  • limit — max results
  • includeAppendix — boolean. When true, the response includes an appendix.{airports,airlines,equipment} block with reference metadata for everything in connections[].

Real Connections-tab request body:

{
  "endpoint": "/route",
  "body": { "json": {
    "departureAirportIata": "DFW",
    "arrivalAirportIata":   "KOA",
    "departureDates": ["2026-04-28"],
    "maxStops": 0,
    "sortBy": "departure_time",
    "includeInterline": false,
    "limit": 100,
    "includeAppendix": true
  }}
}

Each entry in connections[]:

{
  "durationMinutes": 736,
  "score": 1636,
  "flights": [
    { /* leg 1: DFW→SEA, full row shape (see below) */ },
    { /* leg 2: SEA→KOA, full row shape */ }
  ]
}

With maxStops > 0 the server enforces layover validity and chains legs in time order. score is the server's ranking metric (lower = better; appears to combine duration + stops penalty).

Samples:

  • route_explorer_captures/route_DFW_LAS.json — direct only, all DFW→LAS departures on 2026-05-01.
  • route_explorer_captures/route_DFW_KOA_1stop.json — 1-stop itineraries DFW→KOA on 2026-04-28.
  • route_explorer_captures/route_LBB_KOA_2stop.json — 2-stop itineraries LBB→KOA on 2026-04-28.

/route-batch

Not needed for typical use cases — /route with maxStops > 0 does multi-leg directly. Schema for /route-batch not fully reverse-engineered (proxy validates body.base field directly, outside the SuperJSON envelope, and rejected every shape probed). Skip unless a specific need arises.

/flight

Required: carrierCode OR carrierIata, flightNumber, departureDates.

Returns flights for a specific flight number on specific dates. Smaller / sharper than /schedule.

Sample: route_explorer_captures/flight_AA2178.json

/departures

Required: departureAirportIata, departureDates.

All departures from an airport on the given date(s).

Sample: route_explorer_captures/departures_DFW.json — 68KB.

Common flight row shape (returned by /route, /flight, /departures, /schedule)

{
  "flightNumber": 2178,
  "flightSuffix": null,
  "departure": { "airportIata": "DFW", "dateTime": "2026-04-24T16:45:00-05:00", "terminal": "0" },
  "arrival":   { "airportIata": "LAS", "dateTime": "2026-04-24T17:46:00-07:00", "terminal": "1" },
  "durationMinutes": 181,
  "equipmentIata": "32Q",
  "serviceType": "J",
  "isCodeshare": false,
  "stops": 0,
  "stopCodes": null,
  "totalSeats": 196,
  "classes": {
    "first":          { "seats": 0,   "mealCodes": [] },
    "business":       { "seats": 20,  "mealCodes": ["D"] },
    "premiumEconomy": { "seats": 0,   "mealCodes": [] },
    "economy":        { "seats": 176, "mealCodes": ["R","D"] }
  },
  "inFlightService": [12,18,22,23,24,26,27,28,29,31],
  "id": "AA_2178_DFW_2026_04_24",
  "carrierIata": "AA",
  "carrierIcao":  "AAL",
  "isWetlease": false,
  "codeshares": []
}

dateTime values are local times with offset; stable IDs are {carrier}_{flight}_{origin}_{YYYY}_{MM}_{DD}.

/api/weather (GET)

GET /api/weather?endpoint=current&q=iata:LAS&alerts=yes
X-API-Token: <token>

Wraps a weather provider (likely WeatherAPI.com — q=iata:LAS is their syntax). Schema not fully probed.

Public route blobs (no auth at all)

GET https://g80l6xxwjkrjoai7.public.blob.vercel-storage.com/data/routes/{IATA}.json

One precomputed file per airport with weekly aggregate route data. Examples:

  • DFW.json — 271 destinations, 34 airlines, 50,880 weekly flights, 7.15M weekly seats
  • JFK.json — 200 destinations, 75 airlines, 21,611 weekly flights, 3.69M weekly seats (2.5MB file)

Schema (top level): airport, updated (ISO timestamp), stats { destinations, airlines, countries, totalWeeklyFlights, totalWeeklySeats, avgDistance, seasonalRoutes }, routes[].

routes[] entry shape:

{
  "dest": "ORD",
  "airlines": ["AA","F9","NK","UA"],
  "freq": 941,                        // weekly flights aggregated across carriers
  "dist": 1291,                       // miles
  "totalSeats": 163285,               // weekly
  "avgDuration": 155,                 // minutes
  "equipment": ["319","320","321","32N","32Q","738","739","73G","788","7M8","7M9","E70","E7W"],
  "bodyTypes": ["N","W"],             // narrow/wide
  "isSeasonal": true,
  "mealService": "S",                 // single letter code
  "effectiveDates": [{"from":"20270106","to":"20270210"}, ...],
  "daysOfWeek": "1234567"             // 1=Mon .. 7=Sun
}

Updated approx weekly (DFW & JFK both stamped 2026-04-20).

Other domains observed

  • https://emrldtp.com/{entrypoint_config,collect} — Emerald Travel Tech analytics. Cookie consent banner.
  • https://sentry.avs.io/... — self-hosted Sentry (DSN 1c30377da...@sentry.avs.io/20).
  • https://images.stafftraveler.com — image CDN (logos, etc.).
  • https://api.mapbox.com — Mapbox dark-v11/light-v11 styles, public token pk.eyJ1Ijoic3RhZmZ0cmF2ZWxlciIsImEiOiJjbWxyNzVqMzgwN2xhM2ZzNGEzaHVkcDY2In0....