Verified TransJakarta realtime stack: anonymous MQTT bus feed (wss://mqtt.tj.co.id:8084/mqtt), guest REST API (tijeapi.transjakarta.co.id), official GTFS. Cross-capture verification vs third-party tracker, connection recipes, snapshot tool.
4.8 KiB
Analysis — architecture and verification evidence
How the stack works
- Buses carry GPS devices; vehicles are identified by fleet body numbers (
MYS-…,TJ-…,KWK-…,DMR-…,PRM-…; the prefix appears to be operator/depot). - TransJakarta's backend ingests positions and publishes each bus to its MQTT broker; REST state backs the official app.
- Client flow: login (guest works) →
POST /v1/buswith 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. - 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/<grid-cell>/<route-code>/<trip-id>/<bus-body-no>- Real captured example:
/mobile_armada/0021-0014/JAK.49/JAK.49-L03/KWK-1518 <grid-cell>={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).
- Real captured example:
- 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.timestampis 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: <random>@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: <string>.POST /v1/busbody{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_idandroute_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);
radiusunits.