---
title: "Akses API"
summary: "API hanya-baca untuk program lain, seperti spreadsheet akuntan, toko online, atau dasbor sendiri. Membuat kunci API, header X-Api-Key, enam endpoint dengan contoh curl, paginasi, kode galat, dan batas panggilan."
chapter: "Integrasi"
updated: 2026-10-14
source: https://lensiro.com/pos/dokumentasi/akses-api
---

# Akses API

> API hanya-baca untuk program lain, seperti spreadsheet akuntan, toko online, atau dasbor sendiri. Membuat kunci API, header X-Api-Key, enam endpoint dengan contoh curl, paginasi, kode galat, dan batas panggilan.

Lensiro POS punya **API resmi yang hanya membaca**. Dengan API ini, program lain bisa mengambil data toko Anda secara otomatis: spreadsheet akuntan yang menarik penjualan setiap pagi, toko online yang mencocokkan stok, atau dasbor buatan sendiri.

Halaman ini ditulis untuk dua pembaca. Pemilik toko cukup membaca [Membuat kunci API](#membuat-kunci-api). Sisanya untuk developer yang membuat programnya.

> **Hanya membaca.** API tidak bisa membuat, mengubah, atau menghapus apa pun. Nota, pembayaran, dan stok hanya ditulis dari aplikasi Lensiro POS, supaya penomoran nota dan buku stok tetap utuh.

## Apa yang bisa dibaca

| Akses | Isi |
|---|---|
| **Penjualan** | Daftar nota per rentang tanggal, dan satu nota lengkap dengan barang, pembayaran, dan returnya. |
| **Barang & stok** | Katalog barang dengan satuan, harga jual, dan barcode; stok setiap barang. |
| **Pelanggan** | Buku pelanggan dengan jumlah transaksi, total belanja, dan piutang. |
| **Harga modal (HPP)** | Tambahan untuk Barang & stok: harga modal dan nilai stok. |

**Tidak tersedia** lewat API: resep dan data mata pelanggan, kas, pembelian, pesanan lab, dan ringkasan laporan.

Kalau Anda hanya ingin bertanya tentang toko lewat Claude atau ChatGPT, tidak perlu API. Pakai [Koneksi AI](https://lensiro.com/pos/dokumentasi/aslen#sambungkan-claude-atau-chatgpt-anda).

## Membuat kunci API

Program lain masuk ke toko Anda dengan **kunci API**, bukan dengan email dan kata sandi.

1. Buka **Pengaturan → API** (di kelompok **Akses**).
2. Tekan **Buat kunci API**.
3. Isi **Nama**, misalnya *Laporan akuntan* atau *Sinkron toko online*, supaya nanti mudah dikenali.
4. Di **Akses**, centang data yang boleh dibaca: **Penjualan**, **Barang & stok**, **Pelanggan**, atau **Harga modal (HPP)**. Centang seperlunya saja.
5. Tekan **Buat**.
6. Kunci tampil di layar **Simpan kunci ini sekarang**. Tekan **Salin kunci**, tempel di program yang memakainya atau simpan di tempat aman, lalu tekan **Sudah saya simpan**.

> **Kunci hanya ditampilkan sekali.** Lensiro POS tidak menyimpan kuncinya, hanya sidik jarinya. Kalau kunci hilang, cabut lalu buat yang baru.

Halaman yang sama menampilkan **Alamat API** dan sebuah **Contoh** perintah, masing-masing dengan tombol **Salin**. Setiap kunci di **Kunci aktif** menunjukkan namanya, potongan kunci seperti `lpos_Ab3x…9QzK`, akses yang dicentang, siapa yang membuatnya, kapan terakhir dipakai, dan pemakaian hari ini, misalnya *312 / 2.000 hari ini*.

### Siapa yang boleh membuat kunci

Membuat dan mencabut kunci butuh izin **Kelola Kunci API**. Secara bawaan hanya **Pemilik** yang memegangnya. Izin ini tidak termasuk di templat mana pun, termasuk **Admin Toko**. Pemilik bisa memberikannya ke peran lain di **Pengaturan → Pengguna → Role & Izin**. Siapa pun yang memegang izin ini melihat, membuat, dan mencabut semua kunci toko.

### Kunci bekerja atas nama pembuatnya

Kunci membaca **atas nama orang yang membuatnya**, dan tidak pernah lebih dari yang boleh dilihat perannya:

- Akses yang bisa dicentang hanya yang boleh dilihat peran Anda. Yang lain nonaktif dengan keterangan *Peran Anda tidak bisa melihat …*.
- **Harga modal (HPP)** hanya keluar kalau pembuat kunci punya izin **Lihat Harga Modal & HPP** *dan* kuncinya dicentang Harga modal. Kotak itu tidak pernah tercentang dengan sendirinya.
- Kalau izin peran pembuatnya dikurangi kemudian, kunci ikut menyempit pada panggilan berikutnya.
- Kunci **langsung mati** kalau pembuatnya dikeluarkan dari toko, dinonaktifkan, atau kehilangan izin **Kelola Kunci API**. Kalau seorang karyawan berhenti, kunci buatannya ikut berhenti bekerja.

Akses kunci tidak bisa diubah setelah dibuat. Untuk akses yang berbeda, buat kunci baru lalu cabut yang lama. Satu toko boleh punya paling banyak **5 kunci aktif**.

### Mencabut kunci

Tekan **Cabut** pada kuncinya, lalu **Cabut kunci**. Program yang memakainya langsung ditolak. Pencabutan tidak bisa dibatalkan dan tidak menghapus data toko apa pun.

Mengelola kunci butuh internet. Tanpa internet, tombolnya tetap tampil tetapi nonaktif.

## Untuk developer: dasar

**Alamat dasar**

```
https://api.pos.lensiro.com/v1
```

Salin dari **Alamat API** di **Pengaturan → API** supaya pasti sama.

**Autentikasi.** Kirim kunci di header `X-Api-Key` pada setiap panggilan. Contoh di halaman ini menyimpan kunci di variabel `LENSIRO_KEY` lebih dulu:

```bash
export LENSIRO_KEY="lpos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
curl -H "X-Api-Key: $LENSIRO_KEY" https://api.pos.lensiro.com/v1/toko
```

- Kunci selalu diawali `lpos_` dan panjangnya tepat 48 karakter.
- Kunci **hanya** dibaca dari header `X-Api-Key`. Header `Authorization` dan parameter di URL tidak pernah dibaca, jadi jangan menaruh kunci di alamat.
- Panggil API dari **server, skrip, atau otomasi** seperti cron, Google Apps Script, atau Power Query. Peramban memblokir panggilan langsung dari halaman web, dan kunci di kode halaman web bisa dibaca siapa saja.
- Semua endpoint memakai metode `GET`. Metode lain dijawab `405`.
- Setiap panggilan dibatasi 20 detik.

**Format data**

- Uang selalu **bilangan bulat rupiah**: `1400000` berarti Rp 1.400.000.
- Jumlah barang berupa angka dengan paling banyak 3 desimal, misalnya `0.5`.
- Tanggal bisnis (tanggal nota, janji ambil, tanggal bayar) berformat `YYYY-MM-DD` menurut kalender toko (WIB).
- Waktu kejadian (`occurredAt`, `updatedAt`, `canceledAt`, …) berformat RFC 3339 dalam **UTC**, misalnya `2026-10-12T03:15:42Z`.
- Nilai yang tidak ada ditulis `null`, tidak dihilangkan.

**Bentuk jawaban.** Daftar dijawab `{"data": [...], "nextCursor": ...}`. Satu catatan dijawab `{"data": {...}}`.

**Halaman berikutnya.** Selama `nextCursor` tidak `null`, kirim lagi dengan `cursor=<nilai itu>`. Halaman bisa lebih pendek dari `limit`, bahkan kosong (misalnya `/v1/stock` dengan filter `status`), tapi itu belum berarti habis. Daftar baru selesai ketika `nextCursor` bernilai `null`.

## Endpoint

| Endpoint | Akses kunci | Isi |
|---|---|---|
| `GET /v1/toko` | apa saja | Toko, kunci yang dipakai, paket, dan batasnya |
| `GET /v1/sales` | Penjualan | Daftar nota dalam rentang tanggal |
| `GET /v1/sales/{id atau nota}` | Penjualan | Satu nota lengkap |
| `GET /v1/items` | Barang & stok | Katalog barang |
| `GET /v1/stock` | Barang & stok | Stok setiap barang yang punya stok di rak |
| `GET /v1/customers` | Pelanggan | Buku pelanggan |

### GET /v1/toko

Cocok untuk menguji kunci. Tidak butuh akses tertentu.

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" https://api.pos.lensiro.com/v1/toko
```

```json
{
  "data": {
    "toko": { "id": "0192c3a0-1b2c-7d3e-8f40-5a6b7c8d9e0f", "name": "Optik Contoh" },
    "key": {
      "id": "0192f2b1-4d5e-7f60-8a71-b2c3d4e5f607",
      "name": "Laporan akuntan",
      "hint": "lpos_Ab3x…9QzK",
      "scopes": ["sales", "items"]
    },
    "plan": "TIM",
    "historyFloor": null,
    "limits": { "keyPerMinute": 60, "keyPerDay": 2000, "tokoPerMinute": 120, "tokoPerDay": 5000 }
  }
}
```

`scopes` memakai kode akses: `sales` (Penjualan), `items` (Barang & stok), `customers` (Pelanggan), dan `cost` (Harga modal). `historyFloor` berisi tanggal tertua yang bisa dibaca di paket Gratis (juga saat toko tanpa langganan aktif), dan `null` kalau seluruh riwayat terbuka. Lihat [Paket dan riwayat](#paket-dan-riwayat).

### GET /v1/sales

Daftar nota, **terbaru dulu**.

| Parameter | Isi |
|---|---|
| `from`, `to` | Tanggal nota `YYYY-MM-DD`, keduanya ikut dihitung. Bawaannya 30 hari terakhir. Paling panjang **92 hari** per panggilan. |
| `status` | `PESANAN` (barang belum diserahkan) atau `DIAMBIL`. Kosong berarti keduanya. |
| `includeCanceled` | `true` untuk ikut membaca nota yang dibatalkan. Bawaannya tidak. |
| `customerId` | Hanya nota satu pelanggan (UUID dari `/v1/customers`). |
| `q` | Cari di nomor nota, nama atau telepon pelanggan, dan nama barang. |
| `limit`, `cursor` | Lihat [Paginasi](#paginasi). |

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" \
  "https://api.pos.lensiro.com/v1/sales?from=2026-10-01&to=2026-10-14&limit=50"
```

```json
{
  "data": [
    {
      "id": "0192f1a8-3c4e-7d21-8b6a-5e9f0c1d2a3b",
      "nota": "SR-1-00042",
      "status": "PESANAN",
      "customerId": "0192e0b7-2a3d-7c10-9e5f-4b6a7c8d9e01",
      "customerName": "Budi Santoso",
      "customerPhone": "081234567890",
      "cashierId": "0192a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a2b",
      "cashierName": "Rina",
      "agentCustomerId": null,
      "agentName": null,
      "subtotal": 1450000,
      "discount": 50000,
      "ppnRate": 11,
      "ppnMode": "INKLUSIF",
      "ppn": 138739,
      "dpp": 1261261,
      "total": 1400000,
      "returned": 0,
      "netTotal": 1400000,
      "paid": 500000,
      "outstanding": 900000,
      "lineCount": 2,
      "itemCount": 2,
      "orderDate": "2026-10-12",
      "promiseDate": "2026-10-19",
      "pickedUpDate": null,
      "memo": null,
      "canceledAt": null,
      "canceledReason": null,
      "occurredAt": "2026-10-12T03:15:42Z",
      "updatedAt": "2026-10-12T03:15:44Z"
    }
  ],
  "nextCursor": "eyJlIjoic2FsZXMiLCJrIjpbIjIwMjYtMTAtMTJUMDM6MTU6NDIuMTgzWiIsIjAxOTJmMWE4LTNjNGUtN2QyMS04YjZhLTVlOWYwYzFkMmEzYiJdfQ",
  "window": { "from": "2026-10-01", "to": "2026-10-14" }
}
```

Cara membaca angkanya:

- `total` adalah total nota yang tertulis di struk, **sudah termasuk PPN**. `ppn` dan `dpp` memecahnya. `ppnMode` bernilai `INKLUSIF` atau `EKSKLUSIF`.
- `returned` adalah nilai barang yang diretur, dan `netTotal` = `total` − `returned`.
- `paid` adalah jumlah yang sudah dibayar, dan `outstanding` adalah sisa piutangnya.
- `agentCustomerId` dan `agentName` diisi kalau nota memakai referral.
- `window` menyebut rentang tanggal yang **benar-benar dibaca**. Di paket Gratis, `from` bisa dinaikkan ke batas 90 hari.

Rentang lebih dari 92 hari dijawab `400` dengan `"param": "from"`. Bagi menjadi beberapa rentang, misalnya per bulan.

### GET /v1/sales/{id atau nota}

Satu nota lengkap. Isi `{id atau nota}` dengan `id` nota atau nomor notanya (huruf besar dan kecil tidak dibedakan).

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" https://api.pos.lensiro.com/v1/sales/SR-1-00042
```

Jawabannya berisi semua kolom di atas, ditambah `lines`, `payments`, dan `returns`:

```json
{
  "data": {
    "id": "0192f1a8-3c4e-7d21-8b6a-5e9f0c1d2a3b",
    "nota": "SR-1-00042",
    "status": "PESANAN",
    "total": 1400000,
    "paid": 500000,
    "outstanding": 900000,
    "lines": [
      {
        "id": "0192f1a8-3c50-7a11-9b2c-3d4e5f607182",
        "itemId": "0192d4c1-77a2-7b10-9f3e-2a4b6c8d0e1f",
        "code": "FR-TTN-52",
        "name": "Frame Titan 52-18 Hitam",
        "unitName": "pcs",
        "packSize": 1,
        "qty": 1,
        "price": 850000,
        "discount": 0,
        "options": [],
        "lineTotal": 850000,
        "memo": null
      },
      {
        "id": "0192f1a8-3c51-7b22-8c3d-4e5f60718293",
        "itemId": "0192d4c9-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
        "code": null,
        "name": "Lensa RX Progresif",
        "unitName": "pasang",
        "packSize": 1,
        "qty": 1,
        "price": 450000,
        "discount": 0,
        "options": [{ "group": "Indeks", "name": "1.67", "priceDelta": 150000 }],
        "lineTotal": 600000,
        "memo": null
      }
    ],
    "payments": [
      {
        "id": "0192f1a8-3c60-7c33-9d4e-5f6071829304",
        "kind": "PAYMENT",
        "method": "TRANSFER",
        "accountId": "0192b0c1-d2e3-7f40-8a51-b6c7d8e9f001",
        "amount": 500000,
        "date": "2026-10-12",
        "note": null,
        "userName": "Rina",
        "returnId": null,
        "occurredAt": "2026-10-12T03:15:42Z"
      }
    ],
    "returns": []
  }
}
```

*Contoh di atas dipendekkan: jawaban asli memuat semua kolom nota seperti di `/v1/sales`.*

- `lineTotal` = jumlah × (`price` + semua `priceDelta` pilihannya) − `discount`.
- `kind` bernilai `PAYMENT` atau `REFUND`. `method` salah satu dari `CASH`, `TRANSFER`, `QRIS`, `EDC`, atau `LAINNYA`.
- Setiap retur memuat `lines` dengan `saleLineId`, `qty`, `condition` (`LAYAK` atau `RUSAK`), dan `amount`.
- **Resep tidak pernah ikut.** Data mata pelanggan tidak keluar lewat API.
- Nota yang dicari dengan id atau nomornya selalu terbaca, walaupun lebih tua dari batas 90 hari.

### GET /v1/items

Katalog barang, urut nama.

| Parameter | Isi |
|---|---|
| `q` | Cari di nama dan kode barang, atau barcode yang persis sama. |
| `category` | Salah satu kode kategori: `FRAME`, `LENSA_STOK`, `LENSA_RX`, `LENSA_KONTAK`, `KACAMATA_HITAM`, `AKSESORIS`, `JASA`, `LAINNYA`. |
| `limit`, `cursor` | Lihat [Paginasi](#paginasi). |

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" \
  "https://api.pos.lensiro.com/v1/items?category=FRAME&limit=100"
```

```json
{
  "data": [
    {
      "id": "0192d4c1-77a2-7b10-9f3e-2a4b6c8d0e1f",
      "code": "FR-TTN-52",
      "name": "Frame Titan 52-18 Hitam",
      "category": "FRAME",
      "categoryLabel": "Frame",
      "brand": "Titan",
      "units": [{ "name": "pcs", "packSize": 1, "sellPrice": 850000, "barcode": "8991234567895" }],
      "costPrice": null,
      "reorderPoint": 2,
      "stock": 5,
      "stockStatus": "TERSEDIA"
    }
  ],
  "nextCursor": null
}
```

- `category` adalah kode tetap; `categoryLabel` adalah nama yang tampil di aplikasi (`LENSA_KONTAK` tampil sebagai *Softlens*).
- `stock` dan `stockStatus` bernilai `null` untuk `JASA` dan `LENSA_RX`, karena barang itu tidak punya stok di rak.
- `costPrice` hanya terisi untuk kunci dengan akses Harga modal. Lihat [Kunci bekerja atas nama pembuatnya](#kunci-bekerja-atas-nama-pembuatnya).

### GET /v1/stock

Stok setiap barang yang punya stok di rak (semua kategori kecuali Jasa dan Lensa RX), urut nama.

| Parameter | Isi |
|---|---|
| `status` | `HABIS` (stok 0 atau kurang), `MENIPIS` (stok sama dengan atau di bawah titik pesan ulang), atau `TERSEDIA`. |
| `limit`, `cursor` | Lihat [Paginasi](#paginasi). |

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" \
  "https://api.pos.lensiro.com/v1/stock?status=MENIPIS"
```

```json
{
  "data": [
    {
      "itemId": "0192d5e2-3f40-7a51-8b62-c7d8e9f0a1b2",
      "code": "LS-BB-0050",
      "name": "Lensa Blue Block 1.56 -0.50",
      "category": "LENSA_STOK",
      "categoryLabel": "Lensa Stok",
      "stock": 3,
      "reserved": 1,
      "status": "MENIPIS",
      "reorderPoint": 4,
      "costPrice": 95000,
      "inventoryValue": 285000,
      "lastMovementAt": "2026-10-13T08:02:11Z"
    }
  ],
  "nextCursor": null
}
```

- `stock` adalah stok di rak. `reserved` adalah jumlah yang sudah dipesan pelanggan tetapi belum diserahkan; angka ini **tidak** dikurangkan dari `stock`.
- `costPrice` dan `inventoryValue` (nilai stok) hanya terisi untuk kunci dengan akses Harga modal. `costPrice` juga `null` kalau barang itu tidak punya lapisan stok yang masih terbuka. Lihat [Cara stok dihitung](https://lensiro.com/pos/dokumentasi/cara-stok-dihitung).

### GET /v1/customers

Buku pelanggan, urut nama.

| Parameter | Isi |
|---|---|
| `q` | Cari di nama atau nomor telepon. |
| `includeInactive` | `true` untuk ikut membaca pelanggan nonaktif. |
| `limit`, `cursor` | Lihat [Paginasi](#paginasi). |

```bash
curl -H "X-Api-Key: $LENSIRO_KEY" \
  "https://api.pos.lensiro.com/v1/customers?q=budi"
```

```json
{
  "data": [
    {
      "id": "0192e0b7-2a3d-7c10-9e5f-4b6a7c8d9e01",
      "name": "Budi Santoso",
      "phone": "081234567890",
      "email": null,
      "address": "Jl. Melati 12, Solo",
      "memo": null,
      "active": true,
      "customerSince": "2025-03-08",
      "orderCount": 4,
      "totalSpend": 3250000,
      "firstVisit": "2025-03-08",
      "lastVisit": "2026-10-12",
      "outstanding": 900000
    }
  ],
  "nextCursor": null
}
```

## Paginasi

- `limit` menentukan jumlah baris per halaman: bawaannya **50**, paling sedikit 1, paling banyak **100**.
- Kalau masih ada halaman berikutnya, jawaban membawa `nextCursor`. Panggil lagi dengan **parameter yang sama persis**, ditambah `cursor=<nextCursor>`.
- `nextCursor` bernilai `null` di halaman terakhir.
- Kirim cursor apa adanya, tanpa dibaca atau diubah isinya. Cursor dari daftar lain, atau yang rusak, dijawab `400 bad_cursor`. Mulai lagi dari halaman pertama.

```bash
# Ambil semua penjualan 1–14 Oktober, halaman demi halaman (butuh jq)
URL="https://api.pos.lensiro.com/v1/sales?from=2026-10-01&to=2026-10-14&limit=100"
CURSOR=""
while : ; do
  PAGE=$(curl -s -H "X-Api-Key: $LENSIRO_KEY" "$URL${CURSOR:+&cursor=$CURSOR}")
  echo "$PAGE" | jq -c '.data[]'
  CURSOR=$(echo "$PAGE" | jq -r '.nextCursor // empty')
  [ -z "$CURSOR" ] && break
  sleep 1
done
```

> **Tidak ada daftar "yang berubah sejak".** Retur dan pembayaran bisa mengubah nota lama, jadi untuk menjaga salinan tetap benar, baca ulang rentang tanggal yang masih mungkin berubah, misalnya 30 hari terakhir, bukan hanya nota baru.

## Galat

Setiap galat dijawab dengan kode HTTP dan isi JSON seperti ini:

```json
{ "error": "kunci ini tidak dibuat untuk membaca Pelanggan", "code": "scope_missing", "scope": "customers" }
```

`error` adalah kalimat untuk manusia dalam bahasa Indonesia dan bisa berubah. Program sebaiknya membaca `code`, yang tetap.

| HTTP | `code` | Artinya dan yang perlu dilakukan |
|---|---|---|
| 400 | `bad_request` | Parameter salah. `param` menyebut parameternya, misalnya rentang tanggal lebih dari 92 hari. |
| 400 | `bad_cursor` | Cursor rusak atau milik daftar lain. Mulai dari halaman pertama. |
| 401 | `api_key_missing` | Header `X-Api-Key` tidak dikirim. |
| 401 | `api_key_invalid` | Kunci salah, sudah dicabut, atau pembuatnya tidak lagi berhak. Buat kunci baru. |
| 403 | `forbidden` | Peran pembuat kunci tidak boleh melihat data ini. `perm` menyebut izinnya. |
| 403 | `scope_missing` | Kunci tidak dicentang untuk data ini. `scope` menyebut aksesnya. Buat kunci baru dengan akses itu. |
| 404 | `not_found` | Alamat tidak dikenal, atau nota tidak ada di toko ini. |
| 405 | `method_not_allowed` | API hanya menerima `GET`. |
| 423 | `toko_frozen` | Toko sedang dibekukan. Kunci tidak dicabut dan bekerja lagi setelah toko dibuka. |
| 429 | `rate_limited` | Batas panggilan terlampaui. Lihat di bawah. |
| 500 | `internal` | Galat di server. Coba lagi nanti dengan jeda yang makin panjang. |

## Batas panggilan (rate limit)

Supaya layanan tetap cepat untuk semua toko, jumlah panggilan dibatasi. **Batasnya sama di semua paket.**

| Tingkat | Per menit | Per hari |
|---|---|---|
| Setiap kunci | **60** | **2.000** |
| Seluruh toko (semua kunci dijumlah) | **120** | **5.000** |
| Seluruh layanan API | 600, dibagi adil antartoko | — |

- **Menit** dihitung per menit jam (detik 00 sampai 59), bukan 60 detik bergulir.
- **Hari** dimulai pukul **00.00 WIB**.
- Setiap panggilan dengan kunci yang dikenali dihitung, termasuk yang dijawab `400`, `403`, atau `404`. Panggilan dengan kunci salah (`401`) tidak dihitung.
- Panggilan yang ditolak `429` tidak memakan kuota harian.
- Kalau seluruh layanan API sedang ramai, toko yang memanggil paling banyak 30 kali per menit tetap dilayani. Yang ditolak hanya toko yang memanggil lebih dari itu.

### Header batas

Setiap jawaban `/v1` dengan kunci yang dikenali membawa header ini:

| Header | Isi |
|---|---|
| `X-RateLimit-Limit` | Batas menit yang paling ketat saat ini (kunci atau toko) |
| `X-RateLimit-Remaining` | Sisa panggilan di menit ini |
| `X-RateLimit-Reset` | Kapan menit ini berakhir, dalam detik Unix |
| `X-RateLimit-Limit-Day` | Batas hari yang paling ketat (kunci atau toko) |
| `X-RateLimit-Remaining-Day` | Sisa panggilan hari ini |

Dua header `-Day` tidak dikirim kalau panggilan ditolak karena batas menit atau karena layanan sedang ramai.

### Saat dijawab 429

Jawaban `429` menambah dua header, `Retry-After` (berapa detik harus menunggu, paling sedikit 1) dan `X-RateLimit-Scope` (batas mana yang tercapai: `key-minute`, `toko-minute`, `key-day`, `toko-day`, atau `global`):

```
HTTP/2 429
retry-after: 18
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1791947760
x-ratelimit-scope: key-minute
content-type: application/json

{"error":"Terlalu banyak panggilan API — coba lagi dalam 18 detik","code":"rate_limited","scope":"key","window":"minute","limit":60,"retryAfter":18}
```

Isi JSON-nya menyebut `scope` (`key`, `toko`, atau `global`), `window` (`minute` atau `day`), `limit`, dan `retryAfter` dalam detik.

### Cara mundur yang benar

1. **Tunggu sesuai `Retry-After`**, lalu coba lagi. Jangan langsung mengulang: panggilan yang ditolak tetap dihitung di menit itu.
2. Kalau `window` bernilai `day`, kuota hari ini habis. Berhenti sampai lewat **00.00 WIB**.
3. Kalau `scope` bernilai `global` atau jawaban `500`, tunggu dengan jeda yang makin panjang, misalnya 2, 4, lalu 8 detik ditambah sedikit acak, dan berhenti setelah beberapa kali gagal.
4. Pantau `X-RateLimit-Remaining`. Kalau mendekati 0, perlambat sebelum ditolak.
5. Ingat bahwa batas toko dibagi oleh semua kunci toko. Dua program yang sama-sama sibuk bisa saling menghabiskan kuota.

> **Ambil data paling sering 5 menit sekali, dan jangan sepanjang hari.** Untuk laporan, sekali sehari atau sekali per jam sudah cukup. Simpan hasilnya di sisi Anda, dan baca ulang hanya rentang tanggal yang masih bisa berubah.

## Paket dan riwayat

API tersedia di semua paket, termasuk Gratis. Di paket Gratis, juga saat toko tanpa langganan aktif, `/v1/sales` hanya membaca **90 hari terakhir**, sama seperti aplikasi: `from` yang lebih tua dinaikkan otomatis, dan `window.from` serta `historyFloor` di `/v1/toko` menyebut batasnya. Rentang lebih tua tidak ditolak, hanya dipotong. Satu nota yang dicari dengan id atau nomornya selalu terbaca. Lihat [Harga & langganan](https://lensiro.com/pos/dokumentasi/paket-dan-langganan).

## Keamanan

- Perlakukan kunci seperti kata sandi. Simpan di pengaturan rahasia program Anda, jangan di kode yang dibagikan, di spreadsheet yang dibuka banyak orang, atau di halaman web.
- Beri setiap program kuncinya sendiri, dengan akses seperlunya. Kunci yang bocor cukup dicabut tanpa mengganggu program lain.
- Lensiro POS hanya menyimpan sidik jari kunci, bukan kuncinya.
- Cabut kunci yang tidak dipakai lagi. Kunci yang dicabut dihapus permanen setelah 90 hari.

## Yang belum ada

- Menulis lewat API: membuat nota, mengubah stok, atau menambah pelanggan.
- Kas, pembelian, resep, pesanan lab, dan ringkasan laporan.
- Daftar "yang berubah sejak" dan webhook.
- Kedaluwarsa otomatis dan mengganti nama kunci.

Kalau toko Anda butuh salah satunya, ceritakan kepada kami apa yang ingin dikerjakan dengan API.

---

Panduan Lensiro POS · https://lensiro.com/pos/dokumentasi/akses-api
