# 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.
