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>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user