Files
Trey t 6005146e75 Airline integration work: AirlineLoadService updates, docs, JSX scripts
- AirlineLoadService: pass airport DB for timezone-aware date strings,
  add browser-shaped headers for United, expand JetBlue/Alaska/Emirates
  signatures to take origin, log/parse fixes for Korean Air.
- FlightsApp: build AirlineLoadService with the airport DB and inject it.
- JSX: continued WebView-based fetcher work plus updated JSX_NOTES.
- Docs: add AIRLINE_INTEGRATION_GUIDE.md, drop the old AIRLINE_API_SPEC.md,
  add api_docs/ (StaffTraveler reverse-engineering captures + findings).
- Scripts: jsx_cdp_probe, jsx_live_monitor, jsx_swift_smoke for JSX
  protocol exploration.
- .gitignore: exclude airlines/ (local-only APK/IPA reverse-engineering).

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

109 lines
4.6 KiB
Markdown

# StaffTraveler API captures
Real request/response pairs captured from the iOS app `com.stafftraveler.webview` v3.12.0 (build 1880000346) against `api.stafftraveler.com` on 2026-04-22. All captures are by user `3NNPesQMiMRNYnPmuQzh6w2YKyh1` (`stafftraveler@treymail.com`, WN/Southwest, airline ST code `st_SWA`).
## Auth
All command-API calls are authenticated with:
```
Authorization: Bearer <Firebase ID token>
```
Token issued by `securetoken.google.com/stafftraveler-prod`, `aud=stafftraveler-prod`, ~1h TTL. The captured tokens are already expired — do not try to reuse them. To get a new one, sign in via Firebase Auth REST against API key `AIzaSyC2zG6ArnguzzdWsLYV1qjQznma0zl1Q0s` (needs `X-Android-Package` + `X-Android-Cert` headers or iOS SDK).
Only standard headers are sent. No App Check. No SSL pinning. No device/client signing beyond the ID token.
## Endpoint path pattern
```
POST https://api.stafftraveler.com/v1/commands/user/<commandName>
```
Note the `user/` segment — the `api_commands_url` Remote Config value is `https://api.stafftraveler.com/v1/commands/user`, and the command name is appended with `/`. I initially documented this as `/v1/commands/<cmd>` which is wrong.
## Files
### `searchFlightsByRoute_DFW-LAS_*.json`
DFW→LAS direct-only search for 2026-04-22.
Request body:
```json
{
"originIata": "DFW",
"destinationIata": "LAS",
"dates": ["2026-04-22"],
"maxConnections": 0,
"allowNearbyDepartures": false,
"allowNearbyArrivals": false,
"payloadType": "passenger",
"includeMultipleCarriers": false
}
```
Response: `{payload: {directFlights: [...], connectingFlights: [...], numberOfDiscardedFlights, messages, settingsFilteredCount}}`.
Each flight carries three IDs (`id`, `id_v2`, `id_v3`), airline info, local/UTC times, equipment, and a `seatsByClass` **aircraft configuration** (NOT current availability — that's a different read path entirely).
Corrections vs the api_doc's initial guesses:
- `stops` → actually `maxConnections`
- `payloadType: "route"` → actually `"passenger"`
- No `origin`/`destination`/`date` fields — only the `*Iata` variants + `dates` array.
### `createLoadsRequests_AA2178_*.json`
Request loads for AA2178 DFW→LAS 2026-04-22.
**Request body** is a bare JSON array of flight objects (no outer wrapper). Each flight is the complete flight object from the search response with one added field: `isPriorityRequest: bool`.
**Response shape:**
```json
{
"payload": {
"numberOfRequests": 0,
"numberOfRequestsPaidFor": 0,
"numberOfRequestsPaidForPriority": 0,
"hasSufficientCredits": true,
"verifiedRequests": [
{
"flightId": "AA_2178_DFW_2026_04_22",
"isExisting": true,
"isDuplicateForUser": true,
"isRequestUpdateForUser": false,
"hasDeparted": false,
"isCancelled": false,
"isPriorityRequest": false,
"hasRecentLoads": false,
"scheduledFlight": { /* full flight obj */ }
}
]
}
}
```
### Dedup behavior (important)
On this capture, `numberOfRequests: 0` and `isDuplicateForUser: true` — the server recognized this user had already requested this flight and **did NOT charge a credit**. Replaying the same `createLoadsRequests` payload is safe (idempotent).
This means:
- `numberOfRequests` is the count of *newly created* requests in this call.
- `isDuplicateForUser` tells you whether the flight already had an open request for this user.
- **No credit is spent on duplicates.** Useful for polling the current request state without cost.
### `hasRecentLoads: false` — where loads actually come from
`hasRecentLoads` is the flag the app uses to decide whether load data is available for display. It's `false` here because nobody has submitted loads for AA2178 yet. The actual load data (when `hasRecentLoads: true`) does NOT come through the commands API — it arrives via a Firestore realtime listener on a doc that the user's account has permission to read.
To find the exact Firestore path when a flight has loads, one more capture is needed: tap a flight that already has loads populated, and grab the `firestore.googleapis.com/.../Listen/channel` traffic. That will show the watched doc path.
## Known error shapes
Not captured yet:
- What happens when you send an invalid flight ID
- What `createLoadsRequests` returns when credits are insufficient (`hasSufficientCredits: false`)
- What `numberOfRequests > 0` responses look like (fresh request, credit actually spent)
## Replay safety
To replay any of these:
1. Grab a fresh token from the app or by re-signing-in.
2. Swap it into the curl.
3. For `createLoadsRequests`, same payload = dedup-safe. Different payload = may charge credits.