# Analysis — architecture and verification evidence ## How the stack works 1. Buses carry GPS devices; vehicles are identified by fleet body numbers (`MYS-…`, `TJ-…`, `KWK-…`, `DMR-…`, `PRM-…`; the prefix appears to be operator/depot). 2. TransJakarta's backend ingests positions and publishes each bus to its MQTT broker; REST state backs the official app. 3. Client flow: login (guest works) → `POST /v1/bus` with your location → you receive nearby buses **plus MQTT topic wildcards for the map cells around you** → subscribe those on the broker → live positions stream in. REST covers routes/schedules; GTFS covers static geometry. 4. The upstream path from vehicle hardware into the broker is inferred — it is not observable from outside. ## The MQTT feed - Broker: `wss://mqtt.tj.co.id:8084/mqtt` — MQTT 3.1.1 over WebSocket Secure, **anonymous** (no username/password; verified with independent clients). - Topic schema: `/mobile_armada////` - Real captured example: `/mobile_armada/0021-0014/JAK.49/JAK.49-L03/KWK-1518` - `` = `{lat-index}-{lng-index}`, a ~0.01° (~1.1 km) map cell. Verified by probing the discovery API with offset coordinates: ±0.1° of latitude/longitude shifts the matching index by ±10 (index grows southward and eastward). - Retained messages: subscribing delivers ~2,000+ last-known states within seconds, then live updates continue. Per-bus refresh observed at ~8–75 s (some active vehicles faster; many ~25–37 s); delivery is bursty. - Fleet size observed live: 3,300–3,722 buses. ### Payload schema (complete key list, captured verbatim) `latitude`, `longitude`, `snap_latitude`, `snap_longitude`, `bearing`, `estimated_time_next_stops`, `estimated_distance_next_stops`, `curr_stops`, `next_stops`, `prev_stops`, `passengers_status`, `route_code`, `route_long_name`, `route_color`, `route_text_color`, `trip_short_name`, `trip_headsign`, `bus_type`, `type`, `trip_id`, `bus_body_no`, `stops[]`, `timestamp`, `last_update`, `eta_by_parent_stop_id`, `next_parent_gate`, `next_parent_gate_eta`, `next_gate`, `next_gate_eta`, `direction`, `eta`. - `stops[]` items: `name`, `stop_id`, `latitude`, `longitude`, `stop_sequence`, `time_from_prev`, `time_wait`, `headsign`, `parent_stop_id`, `parent_stop_name`, `eta`. - `timestamp` is epoch milliseconds (e.g. `1791797549000`). Stop IDs (`B00436P`-style) match GTFS. - Some fields are null for some vehicles (e.g. gate fields, `passengers_status`, `last_update`). ## The REST API - `POST /v1/auth/login/guest` → `token` + `refresh_token` + `user{email: @guest.tije.com, is_guest: true}`. Token lifetime ~24 h. Headers used: `User-Agent: okhttp/4.12.0`, `X-App-OS: android`, `X-App-Version: 2.10.2`, `X-Device-ID: `. - `POST /v1/bus` body `{latitude, longitude, radius, event: "change_loc"}` → `data { buses[], polyline, session_id, stops[], topics[] }`. `topics` = the MQTT wildcards for nearby cells (e.g. `/mobile_armada/0018-0022/#` …). This is the app's discovery mechanism. - `GET /v1/bus?latitude=&longitude=&radius=` → array of nearby bus records (~15 at radius=1 near Monas; radius units unverified). ## Verification case: oyenza serves this same data Simultaneous captures (node WS client on oyenza's stream + node MQTT client on TJ's broker): - 1,794 buses common to both (of 1,823 seen on the broker in the same window). - 100% agreement on `trip_id` and `route_code`; 99.7% on headsign. - Position deltas: median 8.9 m, p90 42 m, 1,783/1,794 ≤ 100 m — consistent with reading the same moving buses a few seconds apart; no other explanation fits. - Fleet overlap 97.8% over a 75 s window (2,869/2,933); remainder = capture-window skew. - Category taxonomy maps 1:1, including the edge case: the app's whole "other" bucket (2 buses) = the only two untyped buses in the feed. oyenza's own layer (what it adds): fleet-state backend; browser protocol `hello/feed/snap/delta` with positions packed as integers ×10⁵; deltas every 3.00 s (~1,000 position records each); full snapshot 3,722 buses; its own REST extras; a replay mode. Static layer from the official GTFS (240 routes, 698 shapes, 7,493 stops at capture). Its About panel states: "Posisi bus — feed langsung TransJakarta" and "Tidak berafiliasi". ## Confidence - **Established (verified by direct capture):** guest token; both bus endpoints; anonymous MQTT access; topic schema + grid decoding; payload schema; retained behavior; GTFS file; Traccar-based bustrack; oyenza data identity. - **Community-documented (not independently re-verified):** ~105 REST endpoint count; Tara chatbot WS; Kong gateway; refresh-token flow. - **Inferred:** vehicle→broker ingest path; oyenza's exact subscription method (whole-tree vs per-cell — data identity holds either way); `radius` units.