# KasirPro API — Referensi Integrasi / Integration Reference > Base URL: https://api.kasirpro.com — JSON only. Machine-readable endpoint list: GET /v1 (no auth). Human docs: https://api.kasirpro.com/docs.html ## Ringkasan (Bahasa Indonesia) API resmi KasirPro (aplikasi kasir/POS Indonesia) untuk aplikasi apa pun yang bisa memanggil HTTP — backend web/mobile, sistem pemesanan, dashboard, middleware, skrip otomasi, hingga AI agent. - **Autentikasi**: header `Authorization: Bearer kp_live_<64hex>`. Kunci dibuat pemilik toko di https://backoffice.kasirpro.com menu **API Akses**. **Khusus lisensi MAX & Unlimited** — akun PRO ditolak (403 `license_tier`). - **SATU KUNCI = SATU TOKO**: token mengidentifikasi toko sekaligus akun. Tidak ada parameter toko di endpoint mana pun; akun multi toko memakai kunci berbeda per toko (maks 3 kunci/toko). - **Scope per kunci** (dipilih saat dibuat): `produk`, `stok`, `pelanggan`, `laporan`, `transaksi`, `order`. Endpoint menolak kunci tanpa scope-nya (403 `scope_required`). - **Mode kunci**: `Hanya Baca` (semua GET) atau `Baca+Tulis` (juga POST). Kunci hanya-baca ditolak di endpoint POST (403 `write_not_allowed`). - **Rate limit**: 120 request/menit per kunci. Saat 429, hormati header `Retry-After`. - **HTTPS wajib**. Jawaban selalu JSON UTF-8. Tanggal format `YYYY-MM-DD`. Nominal uang = integer Rupiah (tanpa desimal). - **Bentuk error seragam**: `{"error":true,"code":"","message":""}`. Kode: 401 `unauthorized`, 403 `scope_required` | `write_not_allowed` | `license_expired` | `license_tier` | `account_disabled` | `toko_deleted`, 404 `not_found`, 405 `method_not_allowed`, 422 `invalid_param`, 429 `rate_limited`, 500 `server_error`, 502 `store_error`/`xendit_failed`, 503 `not_configured`. - **Tidak disediakan**: HPP/biaya modal tidak pernah dikembalikan; stok, transaksi, kas, dan uang TIDAK bisa ditulis lewat API; tidak ada akses lintas toko. ### Endpoint Baca (GET) - `GET /v1` — dokumentasi mesin (tanpa kunci): daftar endpoint + deskripsi. - `GET /v1/ping` — cek kunci. Jawaban: `{ok, waktu_server}`. - `GET /v1/me` — `{nama, lisensi: "PRO|MAX|Unlimited", aktif_sampai, toko:{id,nama}, mode, scopes[]}`. - `GET /v1/toko` — profil toko kunci: `{data:{id, nama, alamat, kota, telp, timezone}}`. - `GET /v1/produk` [scope produk] — param: `q` (cari nama/SKU), `page` (maks 400), `per` (maks 100). Jawaban `data[]`: `{id, toko, nama, sku, satuan, kategori, harga, aktif}`. Tanpa HPP. - `GET /v1/stok` [scope stok] — param: `q`, `page`, `per`. Jawaban `data[]`: `{produk_id, nama, satuan, stok, resep}`. `resep:true` = produk berbahan: stok fisik lazim 0, stok sebenarnya diturunkan dari bahan (tidak dihitung endpoint ini). - `GET /v1/pelanggan` [scope pelanggan] — param: `q`, `page`, `per`. Jawaban `data[]`: `{id, toko, nama, nohp, alamat, piutang}`. - `GET /v1/laporan/ringkasan` [scope laporan] — param: `from`, `to` (default bulan berjalan; maks 366 hari). Jawaban: `{from, to, total:{trx, omzet}, per_hari:[{tanggal, trx, omzet}]}`. Omzet = total − refund (NET), sama dgn laporan backoffice. - `GET /v1/laporan/produk` [scope laporan] — param: `from`, `to`, `page`, `per`. Jawaban `data[]`: `{produk_id, nama, qty, nilai}` — rumus identik Laporan Penjualan backoffice (qty ikut desimal & retur; nota dibatalkan tidak dihitung). - `GET /v1/transaksi` [scope transaksi] — param: `from`, `to`, `page`, `per`. Jawaban `data[]`: `{id, toko, tanggal, waktu, nota, total, refund, metode, piutang}`. - `GET /v1/transaksi/detail?id=` [scope transaksi] — `{nota:{...breakdown subtotal/diskon/pajak/layanan/ongkir/total/refund/metode}, items:[{nama, qty, qty_retur, total_baris, nilai_retur}]}`. `total_baris` = harga total baris (sudah × qty). - `GET /v1/order/status?id=` [scope order] — `{order_id, paid, status, total}`. `paid:true` setelah QRIS dibayar (status `settlement`). ### Endpoint Tulis (POST, kunci Baca+Tulis) Body JSON, header `Content-Type: application/json`. - `POST /v1/produk` [scope produk] — buat produk. Body: `{nama*, harga*, sku, satuan, deskripsi, kategori_id, stok}`. Jawaban 201: `{ok, produk_id}`. Produk tersinkron ke perangkat kasir ≤ 1 menit. - `POST /v1/produk/ubah` [scope produk] — Body: `{id*}` + hanya field yang mau diubah: `nama, harga, sku, satuan, deskripsi, kategori_id`. - `POST /v1/pelanggan` [scope pelanggan] — Body: `{nama*, nohp, alamat}`. Jawaban 201: `{ok, pelanggan_id}`. - `POST /v1/pelanggan/ubah` [scope pelanggan] — Body: `{id*}` + `nama/nohp/alamat`. - `POST /v1/order` [scope order] — **buat pesanan Toko Online + kode QRIS**. Body: `{nama*, nohp, meja_id, items*:[{produk_id*, varian_id, qty* (1-999), catatan}]}` (maks 100 baris). Jawaban 201: `{ok, order_id, savednota_id, qris:{qris_id, qr_string, amount, orderid, expires_at}}`. Render `qr_string` sebagai QR code untuk dibayar pelanggan (QRIS dinamis, kedaluwarsa ±30 menit). Harga dihitung server mengikuti aturan toko (promo / harga grosir / harga pelanggan). Pesanan tampil di aplikasi kasir sebagai pesanan online; setelah dibayar, nota dibuat otomatis. Pantau lewat `GET /v1/order/status?id=`. ### Praktik terbaik 1. Mulai dengan `GET /v1/me` untuk mengetahui toko, mode, dan scope kunci. 2. Cache hasil `GET /v1/toko` & `GET /v1/produk` — data master jarang berubah. 3. Poll `order/status` maksimal tiap 5–10 detik dan berhenti setelah `paid:true` atau ±30 menit (QRIS kedaluwarsa). 4. Saat 429, tunggu sesuai `Retry-After` (jangan retry agresif). 5. Jangan tanam kunci di kode yang dibagikan; simpan sebagai environment variable. --- ## Summary (English) The official KasirPro API (Indonesian point-of-sale app) for any application that can make HTTP calls — web/mobile backends, ordering systems, dashboards, middleware, automation scripts, and AI agents alike. - **Auth**: `Authorization: Bearer kp_live_<64hex>` header. Keys are created by the store owner at https://backoffice.kasirpro.com under **API Akses**. **MAX & Unlimited licenses only** — PRO accounts are rejected (403 `license_tier`). - **ONE KEY = ONE STORE**: the token identifies both the account and the store. No store parameter exists anywhere; multi-store accounts use a separate key per store (max 3 keys/store). - **Scopes per key** (chosen at creation): `produk` (products), `stok` (stock), `pelanggan` (customers), `laporan` (reports), `transaksi` (transactions), `order` (online orders). Missing scope → 403 `scope_required`. - **Key mode**: `Read-only` (all GET) or `Read+Write` (POST too). Read-only keys get 403 `write_not_allowed` on POST. - **Rate limit**: 120 requests/minute per key. On 429, honor the `Retry-After` header. - **HTTPS required**. Responses are always UTF-8 JSON. Dates are `YYYY-MM-DD`. Money values are integer Indonesian Rupiah (no decimals). - **Uniform error shape**: `{"error":true,"code":"","message":""}` with the codes listed in the Indonesian section above. - **Deliberately not provided**: cost price (HPP) is never returned; stock, transactions, cash and money can NOT be written via the API; no cross-store access. ### Read endpoints (GET) - `GET /v1` — machine-readable endpoint list (no auth). - `GET /v1/ping` — key check. Returns `{ok, waktu_server}`. - `GET /v1/me` — `{nama, lisensi, aktif_sampai, toko:{id,nama}, mode, scopes[]}` (account name, license tier, expiry, the key's store, key mode, scopes). - `GET /v1/toko` — the key's store profile: name, address, city, phone, timezone. - `GET /v1/produk` [produk] — params `q` (search name/SKU), `page` (≤400), `per` (≤100). Rows: `{id, toko, nama, sku, satuan(unit), kategori, harga(sell price), aktif}`. No cost price. - `GET /v1/stok` [stok] — rows `{produk_id, nama, satuan, stok, resep}`. `resep:true` means recipe-based product: physical stock is usually 0 because real availability derives from ingredients (not computed here). - `GET /v1/pelanggan` [pelanggan] — rows `{id, toko, nama, nohp(phone), alamat(address), piutang(receivable)}`. - `GET /v1/laporan/ringkasan` [laporan] — params `from`, `to` (defaults to current month; ≤366 days). Returns daily `{tanggal, trx, omzet}` plus totals. Revenue = total − refund (net), matching the backoffice reports. - `GET /v1/laporan/produk` [laporan] — per-product sales `{produk_id, nama, qty, nilai}` using the exact backoffice Sales Report formula (decimal qty and returns included; voided receipts excluded). - `GET /v1/transaksi` [transaksi] — receipt list `{id, toko, tanggal, waktu, nota(receipt no), total, refund, metode(payment method), piutang}`. - `GET /v1/transaksi/detail?id=` [transaksi] — one receipt with charge breakdown and `items:[{nama, qty, qty_retur, total_baris(line total), nilai_retur}]`. - `GET /v1/order/status?id=` [order] — `{order_id, paid, status, total}`; `paid:true` once the QRIS is settled. ### Write endpoints (POST, Read+Write keys) JSON body, `Content-Type: application/json`. - `POST /v1/produk` — create product `{nama*, harga*, sku, satuan, deskripsi, kategori_id, stok}` → 201 `{ok, produk_id}`. Syncs to cashier devices within ~1 minute. - `POST /v1/produk/ubah` — partial update `{id*}` + any of `nama, harga, sku, satuan, deskripsi, kategori_id`. - `POST /v1/pelanggan` — create customer `{nama*, nohp, alamat}` → 201 `{ok, pelanggan_id}`. - `POST /v1/pelanggan/ubah` — partial update `{id*}` + `nama/nohp/alamat`. - `POST /v1/order` — **create an online order and get a QRIS payment code**. Body: `{nama*, nohp, meja_id, items*:[{produk_id*, varian_id, qty* (1-999), catatan}]}` (≤100 lines). Returns 201 `{ok, order_id, savednota_id, qris:{qris_id, qr_string, amount, orderid, expires_at}}`. Render `qr_string` as a QR code for the customer to pay (dynamic QRIS, expires in ~30 minutes). Prices are computed server-side following the store's rules (promos / wholesale / customer pricing). The order appears in the cashier app as an online order; once paid, the receipt is created automatically. Poll `GET /v1/order/status?id=`. ### Best practices 1. Start with `GET /v1/me` to learn the key's store, mode and scopes. 2. Cache `GET /v1/toko` and `GET /v1/produk` — master data changes rarely. 3. Poll `order/status` at most every 5–10 seconds; stop after `paid:true` or ~30 minutes (QRIS expiry). 4. On 429, wait per `Retry-After`; never retry aggressively. 5. Never embed keys in shared code; store them as environment variables.