# StaffTraveler — URL surface crawl (2026-04-22) Full enumeration of what is and isn't accessible with a valid user Firebase ID token from the `stafftraveler-prod` project. Tested as user `3NNPesQMiMRNYnPmuQzh6w2YKyh1` (WN/`st_SWA`) using fresh tokens from `app.stafftraveler.com`. ## HTTP commands (`api.stafftraveler.com/v1/commands/user/`) All 34 commands confirmed via `createFirebaseCommand` enumeration in the Hermes bundle. Only the `user/` prefix exists — tried `public/`, `internal/`, `admin/`, `v2/`, `flights/`, `loads/`, `system/`, `airline/` → all 404. **Read-ish (test with safe payloads):** - `appActive` — heartbeat. Requires `{localDateIso: ISO8601, appType: "mobile"|"web"}`. Returns `{payload: null}`. - `mobile` — stub, returns `"Command mobile is not implemented"`. - `searchFlightsByCode`, `searchFlightsByRoute` — flight search (free, any route). **Everything else is a mutation:** `createLoadsRequests`, `deleteLoadsRequest`, `reopenLoadsRequest`, `upgradeLoadsRequest`, `requestLockForLoadsRequest`, `unlockLoadsRequest`, `submitLoadsReport`, `reviseLoadsReport`, `flagLoadsReport`, `addAirlineDetails`, `addComment`, `addIdentity`, `addCreditsForPurchase`, `createTip`, `deleteUserAccount`, `disableAllAutoRequestSubscriptions`, `disableFlightStatusUpdates`, `enableFlightStatusUpdates`, `discardHint`, `omitIdentity`, `pinFlight`, `unpinFlight`, `registerUser`, `removeComment`, `reportComment`, `setAutoRequestSubscription`, `submitFeedback`, `unlikeTip`, `updateUserProfile`, `appLogout`. **`api.stafftraveler.com/health`** returns `{message:"ok", serverTime:...}` — open. ## Firestore collections (`firestore.googleapis.com/v1/projects/stafftraveler-prod/databases/(default)/documents`) All reads require `Authorization: Bearer ` + `X-Firebase-GMPID: 1:628258099825:web:35b20eaab4d441894041d0` + `?key=AIzaSyD82Hiqx-jAbHF8faMGSveqLw8CdX8a-Uc`. ### Publicly readable, cross-airline (globally listable or get-by-id) | Path | Content | Size | |------|---------|------| | `__system/maintenance` | service maintenance flags | 1 doc | | `__system/version` | required client versions (android/ios blacklists) | 1 doc | | `__system/supportedCurrencies` | currency support map | 1 doc | | `__derived/nonRevAgreementsBySt` | **Full interline non-rev agreement matrix**: `agreements: { st_XXX: [st_YYY, ...], ... }` | 300 airline keys — pulled and saved to `nonRevAgreementsBySt.json` | | `airlinesBySt/*` | Airline ST-code directory | 300+ docs — pulled to `airlinesBySt_full.json` | | `airlines/*` | Airline IATA directory | publicly readable per-doc (tested `AA`) | | `airportsByIata/*` | Airport IATA directory | publicly readable per-doc | | `airlineNotesBySt/*` | Per-airline non-rev notes | publicly readable per-doc | | `flightEquipment/*` | Aircraft type directory | publicly readable per-doc | | `conversations/*` | Flight comment threads | 404 for empty (rule allows, doc absent); readable where they exist | | `tips/*` | Travel recommendations (restaurants, lounges, etc.) | 5+ docs listable (pagination not tested to completion) | | `autoRequestRecords/*` | **Global** auto-request subscription configs — user uids, trigger schedules, blocked users | 500+ docs listable; flight metadata is NOT in the doc itself (likely derivable from the doc ID hash), but user-activity is | ### Airline-scoped (only MY airline == `st_SWA`) | Query | Behavior | |-------|----------| | `trackedFlights where scheduledFlight.airlineStCode == "st_SWA"` | ✅ 500+ docs (historic WN load requests, all status=closed) | | `trackedFlights where scheduledFlight.airlineStCode == "st_AAL/DAL/UAL/JBU/..."` | ❌ 403 — rules scope to own airline | | `trackedFlights/{flightId_v3}` direct get, same-airline | ✅ readable (even if I'm not the creator) — exposes `currentLoadsReportId`, `reportAuthorList`, `seatsAvailabilityScore` | | `trackedFlights/{flightId_v3}` direct get, other airline | ❌ 403 | ### Strictly user-scoped (me only) | Path | Notes | |------|-------| | `users/{MY uid}` | My profile. Other uids → 403. | | `userCredits/{MY uid}`, `userHints/{MY uid}`, `userRequestCounters/{MY uid}`, `userPriorityRequestCounters/{MY uid}`, `userMetrics/{MY uid}`, `userAchievements/{MY uid}`, `userFlightSearchHistory/{MY uid}` | my state only | | `users/{MY uid}/pinnedFlights`, `users/{MY uid}/connectingFlights` | my subcollections only | | `derivedLoadsReports where flightId == ""` | ✅ readable | | `derivedLoadsReports/{docId}` direct get, my flight | ✅ readable | | `derivedLoadsReports where flightId == ""` | ❌ 403 (cross-checked with direct-get of `dVb4zStjhITceArRgLBB` — a WN862 load doc I don't own) | | `trackedFlights/{flight_I_created}/statusUpdates` subcollection | readable only for flights I own | ### Explicitly denied for listing / querying | Path | Error | |------|-------| | `LIST derivedLoadsReports` without a matching where-filter | 403 | | `LIST trackedFlights` without airlineStCode filter | 403 | | `runQuery` on `derivedLoadsReports`, `trackedFlights`, `scheduledFlights`, `flights`, `flightRecord`, `userRegistrations`, `userEmailVerifications`, `loadsReports` (as singular), `conversations` (list-only, 403) without proper predicates | 403 | | Collection-group queries on sensitive collections | 403 | | `scheduledFlights/{id}`, `flights/{id}`, `flightRecord/{id}` direct get for ANY flight | 403 | ## Other domains on stafftraveler.com (tested unauthenticated GET) | URL | Response | |-----|----------| | `app.stafftraveler.com/` | web app (Next.js); redirects to `/login` if unauth | | `share.stafftraveler.com/` | 200 HTML — share landing page (Next.js). Tried `/request/`, `/flight/`, `/loads/`, `/r/` → all 404 | | `blog.stafftraveler.com/` | blog (not tested deeply) | | `hotels.stafftraveler.com/` | hotels deals (WebView target) | | `carrental.stafftraveler.com/` | car rentals (WebView target, URL pattern `/mobile.html?...`) | | `support.stafftraveler.com/` | support/help | | `shop.stafftraveler.com/` | merch | | `links.stafftraveler.com/` | 404 not-found page (linktree-style, but empty) | | `webhooks.stafftraveler.com/` | Fastify API; `{error:"Not Found"}` on `/`; `/request-password-reset` returns `{error:"Invalid arguments"}` on empty body | | `stafftraveler.com/r/` | Returns generic HTML (any path) | | `images.stafftraveler.com/avatars/*` | Public avatar CDN | ## Goal analysis: "find all filled requests" - **For my own airline (WN/`st_SWA`):** ✅ possible — enumerate all `trackedFlights` with `airlineStCode == "st_SWA"` and `status == "closed"`. 500+ historic records. But actually reading the per-request **load data** (`openSeats.{first,business,economy}.count`, `staffListing.count`) still requires having personally called `createLoadsRequests` on each flight. - **For other airlines:** ❌ blocked. The rule `scheduledFlight.airlineStCode == ` is the hard wall. Verified negatively across 9 airline codes (AAL/DAL/UAL/JBU/ACA/KLM/AFR/BAW/DLH). - **Community-aggregated filled-request data, cross-airline:** not exposed anywhere I found. `autoRequestRecords` comes closest — it shows *who's auto-subscribing*, not what loads were reported. The design is intentional: StaffTraveler's value proposition is a credit-gated access to crowd-sourced data on your own airline's routes. The rules don't let you enumerate outside that scope. ## Useful artifacts saved - `nonRevAgreementsBySt.json` — complete 300-airline interline matrix - `airlinesBySt_full.json` — ST code → airline directory (partial if stopped paging) - `firestore_listen_targets_and_queries.txt` — all Listen-channel queries captured from the web client - `derivedLoadsReports_AA2178_*.json` — single known-working load response capture - `searchFlightsByRoute_DFW-LAS_*.json` / `createLoadsRequests_AA2178_*.json` — HTTP command captures