Unofficial Go SDK and CLI for the TransJakarta open API.
This project is not affiliated with, endorsed by, or sponsored by PT Transportasi Jakarta or the TransJakarta ecosystem. All trademarks belong to their respective owners. Use at your own risk and in accordance with the applicable terms of service.
- REST Client — typed wrapper for 94 endpoints across 16 service groups with JWT auth
- MQTT Client — realtime bus armada subscriber over WebSocket Secure
- Tara Chatbot — WebSocket client for the Tara AI assistant
- CLI — cross-platform binary (
open-tj) with config file + env var support
open-tj wraps three transport surfaces of the TransJakarta backend:
┌─────────────────────────────────────────────────────────┐
│ open-tj SDK │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ REST Client │ │ MQTT Sub │ │ Tara WebSocket │ │
│ │ (94 endpoints│ │ (bus armada │ │ (AI chatbot) │ │
│ │ 16 services) │ │ realtime) │ │ │ │
│ └──────┬───────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
└─────────┼─────────────────┼───────────────────┼──────────┘
│ │ │
▼ ▼ ▼
tijeapi.transjakarta mqtt.tj.co.id chatbot.transjakarta
.co.id (HTTPS/JWT) :8084 (WSS) .co.id (WSS/JWT)
For full architecture details, see docs/architecture.md.
go get github.com/BroNils/open-tjOr build the CLI from source:
git clone https://github.com/BroNils/open-tj.git
cd open-tj
make build
# Binary at dist/open-tjpackage main
import (
"context"
"fmt"
"log"
"github.com/BroNils/open-tj"
)
func main() {
ctx := context.Background()
client := opentj.New()
resp, err := client.Auth.GuestLogin(ctx, opentj.GuestLoginRequest{})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Logged in as guest (id=%d, is_verified=%v)\n",
resp.User.ID, resp.User.IsVerified)
menu, err := client.Menu.List(ctx)
if err != nil {
log.Fatal(err)
}
for _, m := range menu.Data {
fmt.Printf(" %s: %s\n", m.Name, m.Title)
}
}package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/BroNils/open-tj/mqtt"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() { <-sigCh; cancel() }()
sub := mqtt.NewSubscriber()
err := sub.Connect(ctx, mqtt.DefaultTopic, func(topic string, msg *mqtt.BusMessage) {
fmt.Printf("Bus %s on route %s: lat=%f lon=%f\n",
msg.BusBodyNo, msg.RouteCode, msg.Latitude, msg.Longitude)
})
if err != nil {
log.Fatal(err)
}
<-ctx.Done()
sub.Disconnect(250)
}package main
import (
"context"
"fmt"
"log"
"github.com/BroNils/open-tj"
"github.com/BroNils/open-tj/tara"
)
func main() {
ctx := context.Background()
client := opentj.New()
resp, err := client.Auth.GuestLogin(ctx, opentj.GuestLoginRequest{})
if err != nil {
log.Fatal(err)
}
tc := tara.New(resp.Token, tara.WithDeviceID(client.DeviceID()))
err = tc.Connect(ctx, func(msg *tara.ServerMessage) {
if msg.Message != "" {
fmt.Printf("[Tara] %s\n", msg.Message)
}
})
if err != nil {
log.Fatal(err)
}
defer tc.Close()
tc.Send("Hello, I need help")
}Config file (~/.open-tj.yaml):
token: ""
device-id: ""
app-version: "2.11.0"
lang: "en"
output: "json"Environment variables: OPENTJ_TOKEN, OPENTJ_DEVICE_ID, OPENTJ_BASE_URL,
OPENTJ_APP_VERSION, OPENTJ_LANG, OPENTJ_OUTPUT
# Auth
open-tj auth login --email user@example.com --password secret
open-tj auth guest
open-tj auth register --phone +62812... --password secret --name "Name" --email user@example.com
open-tj auth logout --refresh-token <token>
open-tj auth refresh --refresh-token <token>
# Bus
open-tj bus list --lat -6.2 --lon 106.8 --radius 1
open-tj bus track --topic /mobile_armada/#
open-tj bus offline --bus-body-no "BUS-001" --route-id "R1"
# Route
open-tj route list
# Menu
open-tj menu list
# Settings
open-tj settings mqtt-url
open-tj settings royal-trans-config
# MQTT
open-tj mqtt subscribe --topic /mobile_armada/#
# Tara
open-tj tara chat --message "Hello"
# Version
open-tj version- Server-side app version gate:
/v1/campaign/screen/Homeand/v2/reports/listreturn "Please update your app" even with HTTP 200. The library returnsErrAppVersionGatefor these. This cannot be bypassed by spoofingx-app-version. is_verified: false: New and guest accounts returnis_verified: false. OTP verification is required for full functionality. The library surfaces this field but does not bypass it.- Google/Apple login: Endpoint bodies are inferred (unverified).
- 20 endpoints return 500: May require specific parameters not yet known. Implemented with typed request structs, marked with WARNING comments.
- 5 endpoints return 501: Server-side not implemented. Returns
ErrNotImplemented.
| Transport | Endpoint | Protocol |
|---|---|---|
| REST API | tijeapi.transjakarta.co.id |
HTTPS, JWT, Kong gateway |
| MQTT | mqtt.tj.co.id:8084/mqtt |
MQTT 3.1.1 over WSS, anonymous |
| Tara | chatbot.transjakarta.co.id/ws/ |
WebSocket, JWT auth |
The library wraps 94 REST endpoints across 16 service groups. For the complete catalog with paths, status codes, and library methods, see docs/endpoints.md.
| Category | Endpoints | Category | Endpoints |
|---|---|---|---|
| Auth | 12 | RoyalTrans | 9 |
| Settings | 4 | Notification | 3 |
| Menu | 3 | Moter | 3 |
| Route | 6 | Ratings | 2 |
| Maps | 3 | Subscription | 4 |
| Payment | 17 | Reports | 8 |
| Ticket | 10 | User | 7 |
| Voucher | 1 | ||
| Banner | 2 |
In-depth documentation is available in the docs/ directory:
- docs/architecture.md — System architecture, package layout, data flow
- docs/transports.md — Protocol details for REST, MQTT, and Tara
- docs/endpoints.md — Complete endpoint catalog (94 endpoints + 11 unwrapped)
- docs/troubleshooting.md — Known limitations, error handling, FAQ
- docs/contributing.md — How to add a new service, comment style, build/test/lint
See docs/contributing.md for guidelines on adding new services, comment style, and build/test/lint workflow.
MIT