Bab 13 · Developer API
Konvensi Respons & Paginasi
Aturan yang berlaku di semua endpoint: bentuk envelope, snake_case, tanggal UTC, paginasi cursor, sinkronisasi updated_since, dan penanda data terhapus.
Aturan di halaman ini berlaku untuk semua endpoint. Kalau Anda hanya sempat membaca satu halaman teknis, baca yang ini.
Format dasar
- Semua nama field
snake_case—sales_order_number, bukansalesOrderNumber. - Semua tanggal/waktu ISO 8601 UTC, contoh
2026-08-13T03:42:08.000Z. Selalu berakhiranZ. - Semua nilai uang berupa angka Rupiah bulat (integer), tanpa desimal dan tanpa pemisah ribuan.
1350000berarti Rp 1.350.000. - Field yang kosong bernilai
null, bukan string kosong. - Semua respons JSON, dengan
Content-Type: application/json.
Tiga bentuk respons
| Bentuk | Dipakai oleh | Struktur |
|---|---|---|
| List berpaginasi | /sales, /sales/payments, /sales/facets, /sales/{id}/facets, /members, /members/{id}/sales, /members/{id}/point-logs, /warehouse/items, /warehouse/stocks, /warehouse/stock-movements | { "data": [...], "pagination": {...} } |
| Objek tunggal | /sales/{id}, /members/{id}, /warehouse/items/{id} | { "data": { ... } } |
| List pendek tanpa paginasi | /branches, /sales/{id}/items, /sales/{id}/payments, /warehouse/items/{id}/stocks | { "data": [...] } |
Satu pengecualian: /ping membalas { "ok": true, "key_name": "..." } tanpa pembungkus data.
Contoh list berpaginasi:
{
"data": [ { "id": 18234, "...": "..." } ],
"pagination": {
"next_cursor": "eyJ1IjoiMjAyNi0wOC0xM1QwNTozMDoxMi4wMDBaIiwiaSI6MTgyMzR9",
"has_more": true
}
}
Paginasi dengan cursor
limit— jumlah baris per halaman. Default 100, maksimal 500, harus bilangan bulat minimal 1.cursor— penanda posisi, diambil daripagination.next_cursorhalaman sebelumnya.
Polanya:
halaman 1: GET /sales?limit=200
→ has_more = true, next_cursor = "AAA"
halaman 2: GET /sales?limit=200&cursor=AAA
→ has_more = true, next_cursor = "BBB"
halaman 3: GET /sales?limit=200&cursor=BBB
→ has_more = false, next_cursor = null ← berhenti di sini
Aturan penting:
- Bawa serta semua filter di setiap halaman. Cursor hanya menyimpan posisi, bukan filter. Kalau halaman 1 memakai
updated_since, halaman 2 harus memakainya juga. - Cursor bersifat opaque — jangan diurai atau dibuat sendiri, cukup diteruskan kembali. Isinya bisa berubah sewaktu-waktu.
- Tidak ada paginasi offset (
page=/offset=). Cursor aman terhadap data yang bertambah di tengah proses tarik. - Cursor tidak punya masa kedaluwarsa, tapi tidak dimaksudkan disimpan lama. Untuk melanjutkan di lain waktu, simpan
updated_atterbesar dan mulai lagi denganupdated_since.
Urutan data
| Endpoint | Urutan |
|---|---|
| Sebagian besar list | updated_at menaik, lalu id menaik |
/warehouse/stocks | item_id menaik, lalu branch_id menaik |
/warehouse/stock-movements | occurred_at menaik, lalu type, lalu id sumber |
Karena updated_at hanya bergerak maju, baris yang berubah di tengah proses paginasi akan pindah ke belakang — jadi Anda mungkin menerimanya dua kali, tapi tidak akan pernah terlewat. Selama penyimpanan Anda melakukan upsert, ini tidak jadi masalah.
Sinkronisasi inkremental: updated_since
Hampir semua list mendukung updated_since=<ISO datetime>: hanya kirim baris yang berubah sejak waktu tersebut.
- Simpan nilai
updated_atterbesar yang pernah Anda terima. - Pada polling berikutnya, kirim nilai itu sebagai
updated_since. - Upsert hasilnya berdasarkan
id— jangan insert saja.
GET /sales?updated_since=2026-08-13T05:30:12.000Z&include_deleted=true&limit=200
Catatan penting:
- Filternya inklusif (
>=). Baris tepat di batas waktu akan terkirim ulang setiap polling. Ini disengaja supaya tidak ada yang terlewat — upsert membuatnya tidak berbahaya. - Pola ini otomatis menangkap perubahan pada transaksi lama: pembayaran yang menyusul, faktur yang terbit belakangan, atau pembatalan. Filter berdasarkan tanggal transaksi saja akan melewatkan semua itu.
- Endpoint yang tidak mendukung
updated_since:/warehouse/stocksdan/warehouse/stock-movements. Alasannya dijelaskan di halaman API Gudang.
Data terhapus (tombstone)
- Setiap objek yang bisa dihapus punya field
deleted_at—nulluntuk data aktif. - Secara default data terhapus tidak ikut. Tambahkan
include_deleted=trueuntuk menerimanya. - Penghapusan tetap mengubah
updated_at, jadi kombinasiupdated_since+include_deleted=truemengantarkan penghapusan lewat loop polling biasa.
Kalau Anda menyimpan salinan data, selalu poll dengan
include_deleted=true. Tanpa itu, transaksi yang dihapus berhenti terkirim tanpa pemberitahuan, dan salinan Anda diam-diam jadi lebih besar dari kenyataan. Baris dengandeleted_atterisi artinya: hapus, atau tandai batal, di sisi Anda.
Transaksi yang dibatalkan
Berbeda dengan penghapusan: transaksi batal tetap ada dan ikut terkirim secara default (include_canceled=true), dengan status: "CANCELED" dan alasan di canceled_reason. Ini disengaja supaya pembatalan terlihat oleh sistem Anda. Kirim include_canceled=false kalau Anda hanya mau transaksi yang masih berlaku.
Zona waktu
Semua tanggal keluar dalam UTC. WIB = UTC + 7 jam. Jadi "1 Agustus 2026 waktu Indonesia bagian barat" berarti:
transaction_date_from=2026-07-31T17:00:00Z
transaction_date_to=2026-08-01T16:59:59Z
Ini penyebab paling sering angka rekap Anda meleset dibanding laporan di aplikasi, yang memakai waktu lokal. Untuk WITA geser 8 jam, WIT 9 jam.
Tipe dan validasi parameter
| Jenis | Contoh | Aturan | Error kalau salah |
|---|---|---|---|
| Bilangan bulat | branch_id, item_id, category_id, brand_id | bulat, minimal 0 | 400 invalid_parameter |
limit | limit=200 | bulat, 1–500 | 400 invalid_limit |
| Tanggal | updated_since, paid_from | ISO 8601, sebaiknya berakhiran Z | 400 invalid_date |
| Boolean | include_deleted | aktif hanya bila persis true | — (nilai lain dianggap false) |
include_canceled | include_canceled=false | nonaktif hanya bila persis false | — (default true) |
| Enum | type, status | harus salah satu nilai yang diizinkan | 400 invalid_parameter |
cursor | dari next_cursor | opaque | 400 invalid_cursor |
{id} di path | /sales/18234 | bulat, minimal 1 | 400 invalid_id |
Parameter yang tidak dikenal diabaikan diam-diam — salah ketik nama parameter tidak menimbulkan error, tapi juga tidak menyaring apa pun. Periksa lagi kalau hasil filter terasa aneh.
Janji kompatibilitas
Selama masih v1:
- Field baru bisa muncul kapan saja tanpa pemberitahuan. Parser Anda harus mengabaikan field yang tidak dikenal.
- Field yang sudah ada tidak akan diganti nama, dihapus, atau berubah tipe.
- Perubahan yang merusak kompatibilitas akan terbit sebagai
/api/v2, dan/api/v1tetap jalan.
Ringkasan kode status
| HTTP | Arti |
|---|---|
| 200 | Berhasil |
| 400 | Parameter tidak valid |
| 401 | Masalah API key |
| 404 | Objek tidak ditemukan atau sudah dihapus |
| 405 | Metode selain GET |
| 429 | Melebihi batas pemakaian |
| 500 | Kesalahan server — coba ulang dengan jeda bertingkat |
Rincian setiap error.code ada di halaman Kode Error & Pemecahan Masalah.

