{"openapi":"3.1.0","info":{"title":"Commute Data API","description":"Data transit Jakarta dan Jabodetabek: stasiun, lin, pumpunan moda, jadwal, dan tarif dari Commuter Line (KCI), MRT Jakarta, LRT Jakarta, LRT Jabodebek, dan TransJakarta.\n\n### Bentuk response\nSemua response dibungkus object yang sama, baik berhasil maupun gagal. `status` mengikuti HTTP status code-nya. Kalau berhasil, yang terisi `data`. Kalau gagal, yang terisi `error`, lengkap dengan `code` yang bisa dibaca program.\n\n```json\n{ \"status\": 200, \"data\": { } }\n{ \"status\": 404, \"error\": { \"code\": \"NOT_FOUND\", \"message\": \"Not found\" } }\n```\n\n### Identifier\nStation ID bentuknya `{operatorCode}-{stationCode}`, misalnya `KCI-AC`. Kode stasiun cuma unik di dalam satu operator, jadi kebanyakan endpoint minta operator dan kodenya terpisah. Khusus buat cek tarif, pakai ID yang lengkap.\n\n### Autentikasi dan stabilitas\nSemua endpoint yang ada di sini tidak butuh autentikasi. Yang tidak didokumentasikan di sini bukan bagian dari API publik, terutama yang ada di bawah `/_internal`. Itu dibuat khusus buat commute.shiorilabs.id dan bisa berubah sewaktu-waktu tanpa pemberitahuan.\n\n### Lisensi\nDatanya pakai [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/): bebas dipakai dan diolah, asal sumbernya tetap dicantumkan dan hasil olahannya dibagi dengan lisensi yang sama. Kode aplikasinya sendiri MIT.\n\n### Baca versi web-nya\nVersi yang lebih enak dibaca ada di [data.commute.shiorilabs.id/docs](https://data.commute.shiorilabs.id/docs).","version":"1.0.0","contact":{"name":"Commute","url":"https://commute.shiorilabs.id"},"license":{"name":"ODbL-1.0","identifier":"ODbL-1.0"}},"servers":[{"url":"https://api.commute.shiorilabs.id","description":"Production"}],"security":[],"tags":[{"name":"Stasiun","description":"Stasiun, jadwalnya, dan transfer antar stasiun."},{"name":"Lin","description":"Struktur lin, termasuk percabangan dan loop."},{"name":"Pumpunan Moda","description":"Kompleks interchange yang menggabungkan beberapa stasiun."},{"name":"Tarif","description":"Tarif dan rute antara dua stasiun."},{"name":"Operator","description":"Operator transit dan lin yang mereka jalankan."}],"paths":{"/stations":{"get":{"operationId":"getStations","summary":"Daftar semua stasiun","description":"Semua stasiun dari seluruh operator yang bisa dicari. Stasiun yang cuma dipakai buat routing tidak termasuk di sini. Isinya jarang berubah, jadi bisa di-cache di sisi kamu.","tags":["Stasiun"],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Station"}}},"required":["status","data"]}}}}}}},"/stations/{operator}":{"get":{"operationId":"getStationsByOperator","summary":"Daftar stasiun satu operator","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Station"}}},"required":["status","data"]}}}},"404":{"description":"Kode operator tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/stations/{operator}/{stationCode}":{"get":{"operationId":"getStationsByOperatorByStationCode","summary":"Detail stasiun","description":"Detail lengkap satu stasiun, termasuk fasilitas dan koordinatnya. Beda dengan endpoint daftar, di sini stasiun yang cuma dipakai buat routing juga ikut dikembalikan.","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"stationCode","required":true,"schema":{"type":"string","examples":["SUD"]},"description":"Kode stasiun versi operatornya, bukan station id yang lengkap."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"$ref":"#/components/schemas/Station"}},"required":["status","data"]}}}},"404":{"description":"Kode operator atau stasiun tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/stations/{operator}/{stationCode}/timetable":{"get":{"operationId":"getStationsByOperatorByStationCodeTimetable","summary":"Jadwal stasiun","description":"Semua jadwal keberangkatan dalam satu daftar, tanpa dipisah per lin. Kalau butuh yang sudah dikelompokkan per arah seperti papan keberangkatan, pakai `/timetable/grouped`.\n\nBisa dipersempit ke rentang jam pakai `from` dan `to` (format `HH:MM`, waktu lokal Asia/Jakarta). Rentangnya dibulatkan ke jam bulat: `from=12:13&to=15:18` jadi jam 12 sampai 15, mencakup keberangkatan sampai `15:59`. Rentang yang melewati tengah malam ditulis biasa saja, misalnya `from=22:00&to=02:00` buat kereta terakhir. Kalau `from` dan `to` persis sama, yang keluar sehari penuh.","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"stationCode","required":true,"schema":{"type":"string","examples":["SUD"]},"description":"Kode stasiun versi operatornya, bukan station id yang lengkap."},{"in":"query","name":"from","required":false,"schema":{"type":"string","examples":["12:00"]},"description":"Awal rentang jam keberangkatan, format `HH:MM` 24 jam, waktu lokal Asia/Jakarta. Dibulatkan ke bawah ke jam bulat, jadi `12:13` sama saja dengan `12:00`. Kalau cuma `to` yang diisi, rentangnya mulai dari `00:00`."},{"in":"query","name":"to","required":false,"schema":{"type":"string","examples":["15:00"]},"description":"Akhir rentang jam keberangkatan. Dibulatkan ke atas sampai akhir jamnya, jadi `15:18` mencakup semua keberangkatan sampai `15:59` dan yang diminta tidak ada yang hilang. Boleh lebih kecil dari `from` buat rentang yang melewati tengah malam, misalnya `22:00` sampai `02:00`. Kalau persis sama dengan `from`, yang keluar sehari penuh."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"}}},"required":["status","data"]}}}},"400":{"description":"Format `from` atau `to` salah (`INVALID_TIME_RANGE`). Pakai `HH:MM` 24 jam.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}},"404":{"description":"Kode operator atau stasiun tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/stations/{operator}/{stationCode}/timetable/grouped":{"get":{"operationId":"getStationsByOperatorByStationCodeTimetableGrouped","summary":"Jadwal stasiun, dikelompokkan","description":"Jadwal keberangkatan yang sudah dikelompokkan per lin, arah, dan tujuan, sesuai bentuk yang ditampilkan papan keberangkatan. Arahnya ditentukan dari stasiun terakhir tiap perjalanan.\n\nKalau butuh response yang lebih ringan, pakai `compact=1`. Tiap keberangkatan jadi tuple `[tripNumber, minutesSinceMidnight]`, bukan object penuh. Menitnya pakai waktu lokal Asia/Jakarta, dari 0 sampai 1439.\n\nBisa dipersempit ke rentang jam pakai `from` dan `to` (format `HH:MM`, dibulatkan ke jam bulat). Pengelompokannya tetap dihitung dari jadwal sehari penuh, jadi arah dan tujuan yang muncul tidak berubah cuma karena rentangnya dipersempit; yang tersaring keberangkatannya.","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"stationCode","required":true,"schema":{"type":"string","examples":["SUD"]},"description":"Kode stasiun versi operatornya, bukan station id yang lengkap."},{"in":"query","name":"compact","required":false,"schema":{"type":"string","examples":["1"]},"description":"Isi `1` kalau mau keberangkatannya dalam bentuk tuple. Nilai lain akan mengembalikan bentuk penuh."},{"in":"query","name":"from","required":false,"schema":{"type":"string","examples":["12:00"]},"description":"Awal rentang jam keberangkatan, format `HH:MM` 24 jam, waktu lokal Asia/Jakarta. Dibulatkan ke bawah ke jam bulat, jadi `12:13` sama saja dengan `12:00`. Kalau cuma `to` yang diisi, rentangnya mulai dari `00:00`."},{"in":"query","name":"to","required":false,"schema":{"type":"string","examples":["15:00"]},"description":"Akhir rentang jam keberangkatan. Dibulatkan ke atas sampai akhir jamnya, jadi `15:18` mencakup semua keberangkatan sampai `15:59` dan yang diminta tidak ada yang hilang. Boleh lebih kecil dari `from` buat rentang yang melewati tengah malam, misalnya `22:00` sampai `02:00`. Kalau persis sama dengan `from`, yang keluar sehari penuh."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/GroupedTimetable"}},{"type":"array","items":{"$ref":"#/components/schemas/CompactGroupedTimetable"}}]}},"required":["status","data"]}}}},"400":{"description":"Format `from` atau `to` salah (`INVALID_TIME_RANGE`). Pakai `HH:MM` 24 jam.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}},"404":{"description":"Kode operator atau stasiun tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/stations/{operator}/{stationCode}/timetable/{line}":{"get":{"operationId":"getStationsByOperatorByStationCodeTimetableByLine","summary":"Jadwal stasiun untuk satu lin","description":"Jadwal satu lin di satu stasiun. Sama seperti `/timetable`, bisa dipersempit pakai `from` dan `to`.","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"stationCode","required":true,"schema":{"type":"string","examples":["SUD"]},"description":"Kode stasiun versi operatornya, bukan station id yang lengkap."},{"in":"path","name":"line","required":true,"schema":{"type":"string","examples":["C"]},"description":"Kode lin yang mau difilter."},{"in":"query","name":"from","required":false,"schema":{"type":"string","examples":["12:00"]},"description":"Awal rentang jam keberangkatan, format `HH:MM` 24 jam, waktu lokal Asia/Jakarta. Dibulatkan ke bawah ke jam bulat, jadi `12:13` sama saja dengan `12:00`. Kalau cuma `to` yang diisi, rentangnya mulai dari `00:00`."},{"in":"query","name":"to","required":false,"schema":{"type":"string","examples":["15:00"]},"description":"Akhir rentang jam keberangkatan. Dibulatkan ke atas sampai akhir jamnya, jadi `15:18` mencakup semua keberangkatan sampai `15:59` dan yang diminta tidak ada yang hilang. Boleh lebih kecil dari `from` buat rentang yang melewati tengah malam, misalnya `22:00` sampai `02:00`. Kalau persis sama dengan `from`, yang keluar sehari penuh."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"}}},"required":["status","data"]}}}},"400":{"description":"Format `from` atau `to` salah (`INVALID_TIME_RANGE`). Pakai `HH:MM` 24 jam.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}},"404":{"description":"Kode operator, stasiun, atau lin tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/stations/{operator}/{stationCode}/transfers":{"get":{"operationId":"getStationsByOperatorByStationCodeTransfers","summary":"Transfer dari satu stasiun","description":"Sambungan ke stasiun terdekat, termasuk yang beda operator, lengkap dengan jarak jalan kakinya. Transfer `INTERNAL` masih di dalam area berbayar, sedangkan `EXTERNAL` berarti kamu harus tap keluar dulu lalu tap masuk lagi.","tags":["Stasiun"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"stationCode","required":true,"schema":{"type":"string","examples":["SUD"]},"description":"Kode stasiun versi operatornya, bukan station id yang lengkap."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Transfer"}}},"required":["status","data"]}}}},"404":{"description":"Kode operator atau stasiun tidak ditemukan.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/hubs":{"get":{"operationId":"getHubs","summary":"Daftar pumpunan moda","description":"Beberapa stasiun yang dianggap satu tempat karena saling terhubung. Dukuh Atas, misalnya, mencakup Sudirman, BNI City, plus stasiun MRT dan LRT-nya. Diurutkan dari yang paling ramai.","tags":["Pumpunan Moda"],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Hub"}}},"required":["status","data"]}}}}}}},"/hubs/{slug}":{"get":{"operationId":"getHubsBySlug","summary":"Detail pumpunan moda","description":"Satu pumpunan moda lengkap dengan stasiun-stasiun di dalamnya.","tags":["Pumpunan Moda"],"parameters":[{"in":"path","name":"slug","required":true,"schema":{"type":"string","examples":["dukuh-atas"]},"description":"Kunci URL pumpunan moda, bisa diambil dari `/hubs`."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"$ref":"#/components/schemas/Hub"}},"required":["status","data"]}}}},"404":{"description":"Tidak ada pumpunan moda dengan slug tersebut.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/lines/{operator}/{lineCode}":{"get":{"operationId":"getLinesByOperatorByLineCode","summary":"Detail lin","description":"Struktur lengkap satu lin, urut dari stasiun awal. Lin yang lurus cuma punya satu segmen `TRUNK`. Lin yang bercabang seperti loop Cikarang atau ramp Nambo punya segmen tambahan yang menunjukkan di mana cabangnya berpisah dan menyatu lagi.","tags":["Lin"],"parameters":[{"in":"path","name":"operator","required":true,"schema":{"type":"string","examples":["KCI"]},"description":"Kode operator, misalnya `KCI`, `MRTJ`, `TJ`."},{"in":"path","name":"lineCode","required":true,"schema":{"type":"string","examples":["C"]},"description":"Kode lin, bisa diambil dari `/operators`."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"$ref":"#/components/schemas/LineDetail"}},"required":["status","data"]}}}},"404":{"description":"Operator atau lin-nya tidak ditemukan, atau lin-nya belum punya data topologi (`NO_TOPOLOGY`).","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/fares/{from}/{to}":{"get":{"operationId":"getFaresByFromByTo","summary":"Tarif dan rute antara dua stasiun","description":"Mencari rute perjalanan sekaligus menghitung tarifnya. `legs` adalah perjalanan dari sisi penumpang: naik apa saja dan jalan kaki di mana saja. `segments` adalah cara tarifnya dihitung, yang bisa berbeda karena tiap operator menagih per perjalanan di jaringan mereka sendiri. Tarif integrasi sudah dihitung di `totalFare`.","tags":["Tarif"],"parameters":[{"in":"path","name":"from","required":true,"schema":{"type":"string","examples":["KCI-SUD"]},"description":"Station id asal, `{operator}-{code}`."},{"in":"path","name":"to","required":true,"schema":{"type":"string","examples":["MRTJ-LBB"]},"description":"Station id tujuan."},{"in":"query","name":"paymentMethod","required":false,"schema":{"type":"string"},"description":"Menentukan tarif mana yang dipakai. Default-nya tarif kartu uang elektronik biasa."},{"in":"query","name":"at","required":false,"schema":{"type":"string","examples":["2026-07-28T08:00:00Z"]},"description":"Timestamp ISO 8601 buat perjalanannya, dipakai buat menentukan tarif peak atau off-peak. Default-nya waktu sekarang."}],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"$ref":"#/components/schemas/FareResult"}},"required":["status","data"]}}}},"404":{"description":"Salah satu stasiunnya tidak ditemukan, tidak ada rute di antara keduanya, atau asal dan tujuannya sama (`SAME_STATION`).","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}},"500":{"description":"Perhitungan tarif gagal (`DATABASE_ERROR`).","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","examples":[404]},"error":{"$ref":"#/components/schemas/Error"}},"required":["status","error"],"title":"ErrorResponse"}}}}}}},"/operators":{"get":{"operationId":"getOperators","summary":"Daftar operator","description":"Semua operator transit beserta lin yang mereka jalankan. Bagus dipanggil duluan, karena kode dari sini dipakai di seluruh API.","tags":["Operator"],"responses":{"200":{"description":"Berhasil.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"number","description":"Sama dengan HTTP status code-nya.","examples":[200]},"data":{"type":"array","items":{"$ref":"#/components/schemas/OperatorWithLines"}}},"required":["status","data"]}}}}}}}},"components":{"schemas":{"Amenity":{"type":"object","properties":{"type":{"enum":["TOILET","TOILET_ACCESSIBLE","PARKING","BIKE_PARKING","WIFI","CHARGING_STATION","PRAYING_ROOM","ESCALATOR_UNPAID","ESCALATOR_PAID","ELEVATOR_UNPAID","ELEVATOR_PAID","LOCKERS","NURSING_ROOM"],"type":"string","title":"Amenity type","description":"Fasilitas yang ada di stasiun. `TOILET`: Toilet · `TOILET_ACCESSIBLE`: Toilet Difabel · `PARKING`: Parkir · `BIKE_PARKING`: Parkir Sepeda · `WIFI`: WiFi · `CHARGING_STATION`: Charging Station · `PRAYING_ROOM`: Mushola · `ESCALATOR_UNPAID`: Eskalator (Area Umum) · `ESCALATOR_PAID`: Eskalator (Area Berbayar) · `ELEVATOR_UNPAID`: Lift (Area Umum) · `ELEVATOR_PAID`: Lift (Area Berbayar) · `LOCKERS`: Loker · `NURSING_ROOM`: Ruang Menyusui.","examples":["TOILET","ELEVATOR_PAID"]},"text":{"type":"string"}},"required":["type"],"title":"Amenity","description":"Satu fasilitas yang ada di stasiun, kadang dengan keterangan lokasinya."},"Station":{"type":"object","properties":{"id":{"type":"string","description":"Identifier yang stabil, formatnya `{operatorCode}-{stationCode}`. Pakai ini kalau mau cek tarif.","examples":["KCI-SUDB"]},"name":{"type":"string","description":"Nama yang ditampilkan ke penumpang.","examples":["BNI City"]},"officialName":{"type":"string","description":"Nama versi operatornya sendiri, yang sering beda jauh dari nama tampilannya. KCI, misalnya, masih menulis \"SUDIRMAN BARU\" buat stasiun yang kita tampilkan sebagai \"BNI City\". Berguna buat pencarian, karena banyak penumpang masih menyebut stasiunnya dengan nama ini.","examples":["SUDIRMAN BARU"]},"code":{"type":"string","description":"Kode stasiun versi operatornya. Unik di dalam satu operator, tapi belum tentu unik antar operator.","examples":["SUDB"]},"operator":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"lines":{"type":"array","items":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"description":"Lin yang berhenti di stasiun ini, dalam bentuk key ke kamus lin dari `/operators`. Kosong kalau stasiunnya cuma dipakai buat routing."},"regionCode":{"enum":["CGK","BDO","YIA"],"type":"string","title":"Region code","description":"Wilayah, dinamai dari kode IATA bandara besar terdekat. `CGK`: Jabodetabek · `BDO`: Bandung Raya · `YIA`: Jogja-Solo.","examples":["CGK"]},"amenities":{"type":"array","items":{"$ref":"#/components/schemas/Amenity"}},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Lintang WGS84. Null kalau koordinatnya belum disurvei.","examples":[-6.2015]},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"examples":[106.8196]},"score":{"type":"number","description":"Seberapa ramai stasiunnya (0-100), dipakai buat mengurutkan hasil pencarian. Makin tinggi makin ramai. Buat stasiun yang datanya dirilis operator, angkanya dihitung dari jumlah penumpang beneran (gate in-out plus penumpang transit). Sisanya perkiraan dari frekuensi kereta dan bentuk jaringan, bukan hasil pengukuran. TransJakarta belum dihitung, jadi masih 0."},"searchable":{"type":"boolean","description":"False kalau stasiunnya cuma dipakai buat routing dan sengaja disembunyikan dari pencarian. Di response daftar selalu true, karena yang seperti itu sudah difilter duluan."}},"required":["id","name","officialName","code","operator","lines","regionCode","amenities","latitude","longitude","score","searchable"],"title":"Station","description":"Satu stasiun lengkap dengan fasilitas, koordinat, dan lin yang berhenti di sana."},"Error":{"type":"object","properties":{"code":{"type":"string","description":"Kode error yang bisa dibaca mesin, misalnya `SAME_STATION`, `NOT_FOUND`.","examples":["NOT_FOUND"]},"message":{"type":"string","examples":["Not found"]}},"required":["code","message"],"title":"Error","description":"Isi `error` waktu request-nya gagal."},"Schedule":{"type":"object","properties":{"tripNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Nomor perjalanan versi operatornya. Null kalau operatornya tidak menerbitkan nomor.","examples":["5198B"]},"estimatedDeparture":{"type":"string","description":"Waktu lokal, `HH:MM:SS`.","examples":["00:05:30"]},"boundFor":{"type":"string","description":"Stasiun akhir yang dituju perjalanan ini.","examples":["Cikarang"]},"lineCode":{"type":"string"}},"required":["tripNumber","estimatedDeparture","boundFor","lineCode"],"title":"Schedule","description":"Satu keberangkatan: kapan berangkat, menuju ke mana, dan dari peron berapa."},"GroupedTimetable":{"type":"object","properties":{"line":{"type":"string","title":"Line key","description":"Lin yang punya keberangkatan ini.","examples":["KCI:C","MRTJ:M"]},"timetable":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Key yang stabil buat kelompok arah ini."},"label":{"type":"array","items":{"type":"string"},"description":"Label arahnya, berisi nama stasiun. Gabungkan pakai \" / \" kalau mau ditampilkan."},"platformCode":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Info peron yang sudah kita kurasi. Null kalau peronnya belum diketahui."},"destinations":{"type":"array","items":{"type":"object","properties":{"boundFor":{"type":"string","description":"Stasiun akhir yang dituju."},"via":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Terisi kalau ada dua perjalanan ke stasiun akhir yang sama tapi lewat jalur berbeda."},"schedules":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"},"description":"Keberangkatan menuju stasiun akhir itu, urut dari yang paling awal."}},"required":["boundFor","via","schedules"]},"description":"Satu entri per stasiun akhir di arah ini."}},"required":["key","label","platformCode","destinations"],"title":"TimetableDirectionGroup"},"description":"Satu entri per arah keberangkatan."}},"required":["line","timetable"],"title":"GroupedTimetable","description":"Jadwal satu lin, dikelompokkan tiga tingkat: per arah, lalu per stasiun tujuan, lalu daftar keberangkatannya. Ini bentuk yang dipakai papan keberangkatan."},"CompactGroupedTimetable":{"type":"object","properties":{"line":{"type":"string","title":"Line key","description":"Lin yang punya keberangkatan ini.","examples":["KCI:C","MRTJ:M"]},"timetable":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Key yang stabil buat kelompok arah ini."},"label":{"type":"array","items":{"type":"string"},"description":"Label arahnya, berisi nama stasiun. Gabungkan pakai \" / \" kalau mau ditampilkan."},"platformCode":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Info peron yang sudah kita kurasi. Null kalau peronnya belum diketahui."},"destinations":{"type":"array","items":{"type":"object","properties":{"boundFor":{"type":"string","description":"Stasiun akhir yang dituju."},"via":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Terisi kalau ada dua perjalanan ke stasiun akhir yang sama tapi lewat jalur berbeda."},"schedules":{"type":"array","items":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"null"}]},{"type":"number"}]},"minItems":2,"maxItems":2,"title":"CompactSchedule","description":"`[tripNumber, minutesSinceMidnight]`. Nomor perjalanannya null kalau operatornya tidak menerbitkan. Menitnya pakai waktu lokal Asia/Jakarta, dari 0 sampai 1439.","examples":[["5198B",5]]},"description":"Keberangkatan menuju stasiun akhir itu, urut dari yang paling awal."}},"required":["boundFor","via","schedules"]},"description":"Satu entri per stasiun akhir di arah ini."}},"required":["key","label","platformCode","destinations"],"title":"CompactTimetableDirectionGroup"},"description":"Satu entri per arah keberangkatan."}},"required":["line","timetable"],"title":"CompactGroupedTimetable","description":"Sama dengan GroupedTimetable, tapi tiap keberangkatan diringkas jadi tuple `[tripNumber, minutesSinceMidnight]` biar payload-nya lebih kecil."},"StationRef":{"type":"object","properties":{"id":{"type":"string","description":"Identifier yang stabil, formatnya `{operatorCode}-{stationCode}`. Pakai ini kalau mau cek tarif.","examples":["KCI-SUDB"]},"name":{"type":"string","description":"Nama yang ditampilkan ke penumpang.","examples":["BNI City"]},"officialName":{"type":"string","description":"Nama versi operatornya sendiri, yang sering beda jauh dari nama tampilannya. KCI, misalnya, masih menulis \"SUDIRMAN BARU\" buat stasiun yang kita tampilkan sebagai \"BNI City\". Berguna buat pencarian, karena banyak penumpang masih menyebut stasiunnya dengan nama ini.","examples":["SUDIRMAN BARU"]},"code":{"type":"string","description":"Kode stasiun versi operatornya. Unik di dalam satu operator, tapi belum tentu unik antar operator.","examples":["SUDB"]},"operator":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"lines":{"type":"array","items":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"description":"Lin yang berhenti di stasiun ini, dalam bentuk key ke kamus lin dari `/operators`. Kosong kalau stasiunnya cuma dipakai buat routing."}},"required":["id","name","officialName","code","operator","lines"],"title":"StationRef","description":"Rujukan ringkas ke sebuah stasiun. Dipakai di tempat yang cuma butuh nama dan kodenya, tanpa detail lengkap."},"InternalTransfer":{"type":"object","properties":{"id":{"type":"string"},"distanceM":{"type":"number","description":"Jarak jalan kaki dalam meter.","examples":[90]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Petunjuk tambahan buat jalan kakinya. Null kalau tidak ada yang perlu dijelaskan."},"dataType":{"const":"INTERNAL"},"toStation":{"$ref":"#/components/schemas/StationRef"}},"required":["id","distanceM","notes","dataType","toStation"],"title":"InternalTransfer","description":"Sambungan ke stasiun lain yang ada di API ini. `toStation` berisi referensi stasiun lengkap."},"ExternalTransfer":{"type":"object","properties":{"id":{"type":"string"},"distanceM":{"type":"number","description":"Jarak jalan kaki dalam meter.","examples":[90]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Petunjuk tambahan buat jalan kakinya. Null kalau tidak ada yang perlu dijelaskan."},"dataType":{"const":"EXTERNAL"},"toStation":{"type":"object","properties":{"name":{"type":"string"},"operatorName":{"type":"string","description":"Nama operatornya dalam teks biasa, karena layanan di luar API ini tidak punya kode operator."}},"required":["name","operatorName"]}},"required":["id","distanceM","notes","dataType","toStation"],"title":"ExternalTransfer","description":"Sambungan ke layanan yang belum ada di API ini. Tidak ada station id atau lin, cuma nama."},"Transfer":{"oneOf":[{"$ref":"#/components/schemas/InternalTransfer"},{"$ref":"#/components/schemas/ExternalTransfer"}],"title":"Transfer","description":"Dibedakan lewat `dataType`. Kebanyakan INTERNAL, tapi EXTERNAL juga ada, jadi tangani dua-duanya."},"Hub":{"type":"object","properties":{"id":{"type":"string","examples":["HUB-DKA"]},"slug":{"type":"string","description":"Kunci buat URL-nya. Bisa berubah sewaktu-waktu, jadi pakai `id` kalau butuh yang stabil.","examples":["dukuh-atas"]},"name":{"type":"string","examples":["Dukuh Atas"]},"kind":{"enum":["hub","integrated"],"type":"string","description":"`hub`: beberapa stasiun yang namanya berbeda tapi ada di satu kompleks, jadi pengelompokannya membawa informasi. `integrated`: buat penumpang ini satu tempat yang sama, terpisahnya cuma di data antar operator.","examples":["hub"]},"heroImage":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Foto kompleksnya, buat social card dan header halaman."},"lines":{"type":"array","items":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"description":"Semua lin yang bisa dinaiki dari sini, sudah digabung dari seluruh stasiun anggotanya."},"members":{"type":"array","items":{"$ref":"#/components/schemas/StationRef"},"description":"Stasiun-stasiun yang ada di dalamnya, urut sesuai tampilan."}},"required":["id","slug","name","kind","heroImage","lines","members"],"title":"Hub","description":"Beberapa stasiun yang dianggap satu tempat karena saling terhubung. Pumpunan moda sendiri tidak punya koordinat, yang punya stasiun-stasiun di dalamnya."},"Operator":{"type":"object","properties":{"code":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"name":{"type":"string","examples":["Commuter Line"]}},"required":["code","name"],"title":"Operator","description":"Operator transit yang menjalankan lin dan stasiun."},"TransitMode":{"enum":["RAIL","SUBWAY","TRAM","BUS","MONORAIL"],"type":"string","title":"TransitMode","description":"Moda transportasinya, mengikuti `route_type` di GTFS: `RAIL` (2), `SUBWAY` (1), `TRAM` (0), `BUS` (3), `MONORAIL` (12).","examples":["RAIL"]},"Line":{"type":"object","properties":{"name":{"type":"string","examples":["Lin Cikarang"]},"lineCode":{"type":"string","examples":["C"]},"colorCode":{"type":"string","description":"Warna hex buat roundel dan branding lin ini.","examples":["#25B8EB"]},"mode":{"$ref":"#/components/schemas/TransitMode"},"searchable":{"type":"boolean","description":"False kalau lin ini cuma dipakai buat routing dan sengaja disembunyikan dari pencarian dan sitemap. Lin seperti ini tetap ada di kamus lin, karena stasiun masih merujuk ke sana, tapi halaman detailnya belum tersedia."}},"required":["name","lineCode","colorCode"],"title":"Line","description":"Satu lin, dengan kode dan warna yang dipakai di roundel-nya."},"LineStation":{"type":"object","properties":{"id":{"type":"string","examples":["KCI-SUD"]},"code":{"type":"string"},"name":{"type":"string","description":"Nama tampilan."},"stationNumber":{"type":"string","description":"Label posisi di sepanjang lin, misalnya `C13`.","examples":["C12"]},"isInterchange":{"type":"boolean","description":"True kalau stasiun ini titik pindah ke lin lain."},"otherLines":{"type":"array","items":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"description":"Lin lain dari operator yang SAMA yang berhenti di sini, berupa line key. Lin yang sedang dibuka tidak termasuk. Sambungan lintas operator ada di transfer stasiunnya."}},"required":["id","code","name","stationNumber","isInterchange","otherLines"],"title":"LineStation","description":"Satu stasiun di sepanjang lin, urut dari stasiun awal."},"LineSegment":{"type":"object","properties":{"kind":{"enum":["TRUNK","CONTINUATION","RAMP","LOOP"],"type":"string","description":"`TRUNK`: jalur utama. `CONTINUATION`: cabang yang meneruskan jalur utama dan terbaca sebagai mainline. `RAMP`: cabang samping yang memisah. `LOOP`: cabang yang menyatu kembali ke jalur utama.","examples":["TRUNK"]},"joinsAtCode":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Kode stasiun tempat cabang ini berpisah dari jalur utama; null buat `TRUNK`."},"stations":{"type":"array","items":{"$ref":"#/components/schemas/LineStation"}}},"required":["kind","joinsAtCode","stations"],"title":"LineSegment","description":"Satu ruas lin. Lin lurus cuma punya satu ruas `TRUNK`; lin bercabang punya beberapa."},"LineDetail":{"type":"object","properties":{"operator":{"$ref":"#/components/schemas/Operator"},"line":{"$ref":"#/components/schemas/Line"},"segments":{"type":"array","items":{"$ref":"#/components/schemas/LineSegment"},"description":"Struktur lin secara urut. Lin sederhana cuma punya satu `TRUNK`; lin bercabang menambah segmen lain."}},"required":["operator","line","segments"],"title":"LineDetail","description":"Struktur lengkap satu lin: segmen-segmennya, plus stasiun yang dilewati tiap segmen."},"FareStation":{"type":"object","properties":{"id":{"type":"string","examples":["KCI-SUD"]},"name":{"type":"string","examples":["Sudirman"]}},"required":["id","name"],"title":"FareStation","description":"Stasiun di dalam hasil pencarian tarif."},"FareLineRef":{"type":"object","properties":{"line":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"headsign":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stasiun akhir yang dituju perjalanan ini."}},"required":["line","headsign"],"title":"FareLineRef","description":"Lin yang dipakai di satu tahap perjalanan."},"FareRideLeg":{"type":"object","properties":{"type":{"const":"RIDE"},"line":{"type":"string","title":"Line key","description":"`OPERATOR:CODE`, dicocokkan ke kamus lin dari `/operators`.","examples":["KCI:C","MRTJ:M"]},"operator":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"from":{"$ref":"#/components/schemas/FareStation"},"to":{"$ref":"#/components/schemas/FareStation"},"stationCount":{"type":"number","description":"Jumlah stasiun yang dilewati, termasuk stasiun awal dan akhir."},"stops":{"type":"array","items":{"$ref":"#/components/schemas/FareStation"},"description":"Daftar stasiun lengkap dan urut, dari tempat naik sampai tempat turun."},"headsign":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stasiun akhir yang dituju. Null kalau tidak bisa ditentukan."},"distanceM":{"type":"number","description":"Jarak tempuh tahap ini dalam meter."},"serviceLines":{"type":"array","items":{"$ref":"#/components/schemas/FareLineRef"},"description":"Cuma muncul di jalur yang dipakai bersama beberapa lin, jadi naik yang mana pun tetap sampai. Lin utamanya ditaruh paling depan."}},"required":["type","line","operator","from","to","stationCount","stops","headsign","distanceM"],"title":"FareRideLeg","description":"Tahap perjalanan naik kendaraan, dari stasiun naik sampai stasiun turun."},"FareTransferLeg":{"type":"object","properties":{"type":{"const":"TRANSFER"},"from":{"$ref":"#/components/schemas/FareStation"},"to":{"$ref":"#/components/schemas/FareStation"},"distanceM":{"type":"number","description":"Jarak jalan kaki dalam meter."},"fare":{"type":"number","description":"Cuma ada di koridor berbayar. Transfer jalan kaki biasa gratis, jadi tidak punya ini."},"corridorLabel":{"type":"string","description":"Nama koridor yang dilewati waktu pindah, kalau ada."}},"required":["type","from","to","distanceM"],"title":"FareTransferLeg","description":"Tahap pindah kendaraan, biasanya jalan kaki antar peron atau antar stasiun."},"FareLeg":{"oneOf":[{"$ref":"#/components/schemas/FareRideLeg"},{"$ref":"#/components/schemas/FareTransferLeg"}],"title":"FareLeg","description":"Satu tahap perjalanan. Dibedakan lewat `type`."},"FareSegment":{"type":"object","properties":{"operator":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"from":{"$ref":"#/components/schemas/FareStation"},"to":{"$ref":"#/components/schemas/FareStation"},"fare":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Tarif buat segmen ini dalam rupiah. Null kalau tidak bisa dihitung."}},"required":["operator","from","to","fare"],"title":"FareSegment","description":"Satu bagian perjalanan yang ditagih. Tiap operator menagih per perjalanan menerus di jaringan mereka sendiri, makanya segment dan leg tidak selalu satu lawan satu."},"FareResult":{"type":"object","properties":{"from":{"$ref":"#/components/schemas/FareStation"},"to":{"$ref":"#/components/schemas/FareStation"},"legs":{"type":"array","items":{"$ref":"#/components/schemas/FareLeg"},"description":"Perjalanan dari sisi penumpang: naik apa saja dan transfer di mana saja."},"segments":{"type":"array","items":{"$ref":"#/components/schemas/FareSegment"},"description":"Perjalanan dari sisi penagihan tarif."},"totalFare":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total dalam rupiah, sudah termasuk batas tarif integrasi. Null kalau tarif buat pasangan stasiun ini tidak bisa dihitung.","examples":[14000]},"totalDistanceM":{"type":"number","description":"Total jarak seluruh perjalanan dalam meter."},"transferCount":{"type":"number","description":"Berapa kali harus pindah kendaraan."}},"required":["from","to","legs","segments","totalFare","totalDistanceM","transferCount"],"title":"FareResult","description":"Hasil pencarian rute: perjalanannya seperti apa (`legs`) dan tarifnya dihitung bagaimana (`segments`)."},"OperatorWithLines":{"type":"object","properties":{"code":{"enum":["KCI","MRTJ","LRTJ","LRTJBDB","TJ","APCGK"],"type":"string","title":"Operator code","description":"Operator transit. `KCI`: Commuter Line · `MRTJ`: MRT Jakarta · `LRTJ`: LRT Jakarta · `LRTJBDB`: LRT Jabodebek · `TJ`: TransJakarta · `APCGK`: Kalayang Bandara. `NUL` cuma placeholder internal dan tidak pernah muncul di response.","examples":["KCI","MRTJ"]},"name":{"type":"string","examples":["Commuter Line"]},"url":{"type":"string","description":"Situs resmi operatornya. Sama dengan `agency_url` di GTFS.","examples":["https://kci.id/"]},"timezone":{"type":"string","description":"Zona waktu yang dipakai semua jadwal operator ini. Sama dengan `agency_timezone` di GTFS, dan selalu `Asia/Jakarta` di sini.","examples":["Asia/Jakarta"]},"lang":{"type":"string","description":"Bahasa yang dipakai buat nama stasiun dan lin. Sama dengan `agency_lang` di GTFS.","examples":["id"]},"mode":{"$ref":"#/components/schemas/TransitMode"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/Line"}}},"required":["code","name","url","timezone","lang","mode","lines"],"title":"OperatorWithLines","description":"Operator beserta seluruh lin yang dijalankannya. Ini yang dikembalikan `/operators`."}}}}