ba0688a412
route-explorer's /api/token sits behind invisible Cloudflare Turnstile
that requires Apple's Private Access Token attestation. Third-party
iOS apps don't qualify for PAT issuance, and Linux Docker containers
can't pass it either (cross-OS fingerprint, even with patchright /
Camoufox). Migrates direct-flight search to FlightAware; multi-stop
and where-can-I-go remain via embedded SFSafariViewController.
- FlightAwareScheduleClient — scrapes route.rvt + trackpoll JSON for
real schedules without auth. T+0..2 day window. Tests against
captured HTML fixtures.
- BlobRouteClient — pulls the public Vercel blob route catalog
route-explorer's frontend reads (no auth, no Turnstile).
- DiagnosticLogger + LoggingURLSessionDelegate + DiagnosticsView —
device-shareable forensic trace. Boot header captures device, OS,
locale, UA; share-sheet export of session logs.
- TurnstileDebugView — live WKWebView gate inspector. Used to prove
the PAT-entitlement gap on a real device.
- RouteExplorerBrowserView — SFSafariViewController wrapper. Real
Safari clears Turnstile naturally; the in-app browser opens at
pre-filled search URLs. Surfaced from Search ("Open in
route-explorer") and Settings → Tools.
- RouteExplorerTokenStore + RouteExplorerSetupView — bookmarklet
capture flow (token round-tripped via flights://routeexplorer-token
URL scheme). Kept dormant for future use.
backend/ — Docker proxy attempts (Playwright, patchright, Camoufox).
All fail on Linux because Cloudflare auto-denies before the Turnstile
widget renders. Documented; kept as scaffolding for a future paid-
solver integration.
scripts/probe_flightaware.py — reference algorithm for the FA path.
scripts/probe_nodriver.py — local-Mac sanity check confirming the
gate clears with real macOS Chrome (proves the blocker is
fingerprint-level, not network-level).
82 lines
7.2 KiB
Markdown
82 lines
7.2 KiB
Markdown
# TSA Wait-Times Data Feasibility Research
|
||
|
||
**Date:** 2026-05-31
|
||
**Project:** Flights iOS app
|
||
**Question:** Is there a real, free, key-less, server-less source of TSA wait-time data we can ship?
|
||
**Short answer:** **No.** Recommend Option B — keep the bundled baseline and **honestly relabel** the UI.
|
||
|
||
---
|
||
|
||
## URLs investigated
|
||
|
||
| URL | Status | Notes |
|
||
|---|---|---|
|
||
| `https://www.tsa.gov/travel/security-screening/whatcanibring` | 403 to WebFetch | Page is the "What Can I Bring" tool; not a wait-time source. |
|
||
| `https://www.tsa.gov/mobile` | 200 | Marketing page for MyTSA app. No public API surface. |
|
||
| `https://apps.tsa.dhs.gov/MyTSAWebService/GetTSOWaitTimes.ashx` | **302 → tsa.gov** | The historically documented endpoint (used by GitHub `taitcha/tsa-mashup` and others) **no longer returns data**. Confirmed via `curl -sI -L`: BigIP load balancer issues a `302 Moved Temporarily` to `http://www.tsa.gov` with `Content-Length: 0`. The MyTSAWebService is effectively retired. |
|
||
| `https://www.tsa.gov/data/apcp.xml` | **404 Not Found** | Companion airport-checkpoint metadata file the legacy API depended on. Confirmed dead via `curl`: `HTTP/2 404 Not found`. |
|
||
| `https://www.dhs.gov/mytsa-api-documentation` | 403 to WebFetch | Documentation page still indexed but the underlying service is gone. |
|
||
| `https://catalog.data.gov/dataset/tsa-wait-times-january-2006-to-december-2015` | 200 | Archive only — Jan 2006 through Dec 2015. **No real-time component.** Useful for historical research, not for live wait estimates. |
|
||
| `https://catalog.data.gov/dataset/tsa-foia-reading-room-weekly-passenger-throughput-data` | 200 | Weekly *throughput* (passenger counts), not wait minutes. Lagged. Not appropriate for "wait at checkpoint right now." |
|
||
| `https://www.tsawaittimes.com/api/` | 200 | **Third-party paid API** run by TayTech LLC (Wisconsin). $49.95/mo, $479.52/yr. Requires API key + paid sub. Self-disclaimer: "this website is not owned or affiliated with the TSA" and "wait times are estimates and may not be reflective of the actual experience." Data source is a mix of "government data, traveler contributions, and internal data." **Violates the project's no-paid-API constraint.** |
|
||
| `https://apps.apple.com/us/app/mytsa/id380200364` | 200 | MyTSA v4.5.0, last updated **2024-12-09**. App itself still available but consumes the same dead internal feed. |
|
||
| `https://www.dhs.gov/check-wait-times` | 200 | Marketing landing page. Points to MyTSA app only. |
|
||
| `https://github.com/taitcha/tsa-mashup` | 200 | Open-source Python 2.7 demo. Hardcodes the now-dead `GetTSOWaitTimes.ashx` endpoint. Repo is unmaintained. |
|
||
| `https://www.ksat.com/news/local/2026/03/25/where-to-find-airport-security-wait-times-while-tsa-app-is-down/` | 200 | News article (March 2026): TSA website and MyTSA app currently show "no longer being updated" warnings due to government shutdown / staffing. Even the official channel is unreliable right now. |
|
||
| `https://developer.apple.com/wallet/` | 200 | PassKit boarding-pass integration with iOS 26 Maps gives walking-time-to-gate, **not security wait time.** No PassKit/Maps API exposes TSA queue data to third-party apps. |
|
||
|
||
---
|
||
|
||
## What does and does not exist
|
||
|
||
**Does not exist:**
|
||
- A free, key-less, public, real-time TSA wait-times API for general developer use.
|
||
- A `data.gov` real-time feed (only historical archives through 2015 + weekly throughput counts).
|
||
- An Apple system framework that exposes TSA wait times to third-party apps (PassKit/Maps surface gate-walk timings only).
|
||
- A working successor to `GetTSOWaitTimes.ashx`.
|
||
|
||
**Does exist (but unusable for this project):**
|
||
- `tsawaittimes.com` — paid, third-party, partially crowdsourced, not affiliated with TSA. Violates the no-paid-API rule and would mislead users with non-official data branded as TSA.
|
||
- Historical data.gov archive (2006–2015) — possibly useful for refining the bundled baseline once, but not for live use.
|
||
- MyTSA consumer app — only useful when a human reads the screen, not as a programmatic source. Also currently warning users that its own data is stale.
|
||
|
||
---
|
||
|
||
## Recommendation: **Option B — keep the bundled baseline, relabel honestly**
|
||
|
||
Reasons to keep rather than drop:
|
||
1. The current `tsa_wait_baseline.json` already gives a reasonable order-of-magnitude estimate for ~25 hubs by hour-of-day and weekday/weekend. For an airline employee planning a standby trip, "Tuesday 6am at ATL averages ~22 min" is genuinely useful context even if it isn't live.
|
||
2. A nonrev traveller looking at a flight detail benefits from *any* signal about checkpoint pressure, provided it is clearly labelled as a historical typical-value rather than a live measurement.
|
||
3. Dropping the feature would lose information that we *do* have honestly. The fix is in the wording, not in the data.
|
||
|
||
What must change in the UI (Phase 3 fix, not this phase):
|
||
|
||
The `basis` string surfaced by `TSAWaitTimesClient.waitEstimate(...)` is what `LiveFlightDetailSheet` displays. Today it says `"baseline (weekday)"`, `"baseline (weekend)"`, or `"estimated"`. These are not clear enough about provenance. Replace with **exact** wording:
|
||
|
||
| Current basis | Replace with (verbatim) |
|
||
|---|---|
|
||
| `"baseline (weekday)"` | `"Typical wait — weekday avg, not live"` |
|
||
| `"baseline (weekend)"` | `"Typical wait — weekend avg, not live"` |
|
||
| `"estimated"` | `"Rough estimate — no live TSA feed"` |
|
||
|
||
And in `LiveFlightDetailSheet`, the section header or footnote near the TSA row should read:
|
||
|
||
> **Wait times are historical typicals.** TSA does not publish a public real-time feed; values shown are hour-of-day averages, not live measurements.
|
||
|
||
That sentence is the honest disclaimer to surface in the sheet. It can be a `.footnote` under the TSA row or part of an info `Label`.
|
||
|
||
---
|
||
|
||
## Assumptions the reviewer should verify
|
||
|
||
1. The `tsa_wait_baseline.json` file shipped in `Flights/Resources/` is **hand-curated** per the `TSAWaitTimesClient` docstring. I did **not** verify the actual numbers in that JSON against historical TSA reports. If you want defensibility, the next pass should re-source the buckets from the data.gov 2006–2015 archive (or the weekly throughput dataset's recent values, used as a *busyness proxy* rather than literal wait minutes) and add a `source:` field to the JSON.
|
||
2. The `GetTSOWaitTimes.ashx` endpoint redirects to the TSA homepage **as observed today (2026-05-31)** from this network. There is a small chance this is a transient outage tied to the ongoing shutdown rather than permanent retirement — but the airport-metadata XML being 404, plus public reporting that TSA's own app is showing "not being updated" warnings, makes me confident this is structural, not transient. If you want to be doubly safe, code the rewrite to *try* the endpoint with a short timeout and fall back to baseline silently — but I would not recommend spending the effort given how unreliable the upstream has proven.
|
||
3. The `tsawaittimes.com` pricing was scraped from their public marketing page. If you ever reconsider, re-verify current pricing before paying.
|
||
|
||
---
|
||
|
||
## File summary
|
||
|
||
- `/Users/m4mini/Desktop/code/Flights/notes/tsa_research.md` — this report (new)
|
||
- `/Users/m4mini/Desktop/code/Flights/Flights/Services/TSAWaitTimesClient.swift` — read only, **not modified** (per task instructions; Phase 3 will apply the relabel based on this recommendation)
|