Logo
Daftar Isi

Bab 13 · Developer API

Panduan Integrasi & Contoh Kode

Resep polling yang benar (backfill, updated_since, upsert, tombstone) plus contoh kode siap pakai untuk Google Apps Script, Python, dan Node.js.

Halaman ini adalah cara yang didukung untuk menarik data Lensiro secara berkala. Kalau integrasi Anda mengikuti pola di bawah, ia tetap benar meski transaksi diedit, dibatalkan, atau dihapus.

Pola dasar: empat langkah

1. VALIDASI   -> /ping dan /branches, sekali di awal
2. BACKFILL   -> tarik riwayat per rentang bulanan, satu kali saja
3. POLLING    -> tarik perubahan dengan updated_since, berkala
4. UPSERT     -> simpan berdasarkan id; hapus baris yang punya deleted_at

1. Validasi

curl -H "Authorization: Bearer $KEY" "$BASE/ping"
curl -H "Authorization: Bearer $KEY" "$BASE/branches"

Simpan pemetaan cabang dari /branches supaya branch_id di data lain bisa diterjemahkan tanpa hardcode.

2. Backfill awal, sekali saja

Tarik riwayat per bulan supaya setiap request ringan dan mudah diulang kalau gagal di tengah jalan:

GET /sales?transaction_date_from=2026-01-01T00:00:00Z
         &transaction_date_to=2026-01-31T23:59:59Z
         &include_deleted=true
         &limit=200
-> ikuti next_cursor sampai has_more = false
-> lanjut ke bulan berikutnya

Beri jeda sekitar satu detik antar halaman supaya tidak menyenggol batas 60 request per menit.

3. Polling berkala, misalnya tiap jam

last = nilai updated_at terbesar yang tersimpan
       (kalau belum ada, pakai waktu selesai backfill)

GET /sales?updated_since={last}&include_deleted=true&limit=200
-> ikuti next_cursor sampai has_more = false
-> untuk setiap baris:
     kalau deleted_at terisi -> hapus atau tandai batal di sistem Anda
     kalau tidak             -> UPSERT berdasarkan id
-> simpan updated_at terbesar yang baru

Pola yang sama berlaku untuk /sales/payments, /sales/facets, /members, dan /warehouse/items.

Kenapa harus upsert, bukan insert? Karena transaksi bisa berubah setelah pertama kali Anda lihat — pembayaran menyusul, faktur terbit belakangan, item diperbaiki, atau transaksi dibatalkan. updated_since akan mengirim transaksi itu lagi, dan salinan Anda harus menimpanya, bukan menduplikasinya.

Simpan updated_at terbesar hanya setelah seluruh halaman selesai ditarik. Kalau disimpan di tengah jalan lalu proses gagal, polling berikutnya akan melompati data yang belum sempat masuk.

4. Yang tidak mengikuti pola ini

DataPola yang benar
/warehouse/stocksSnapshot — tarik ulang seluruhnya secara berkala, timpa tabel Anda
/warehouse/stock-movementsTarik ulang jendela 7 hari terakhir, upsert berdasarkan id
Saldo poin pelangganTidak mengubah updated_at member — poll /members/{id}/point-logs atau full refresh berkala

Resep siap pakai

KebutuhanCara
Omzet harian per cabang/sales, kelompokkan net_total per tanggal (setelah konversi ke WIB) dan branch.id; buang status = "CANCELED"
Arus kas per metode bayar/sales/payments, kelompokkan amount per paid_at dan type; pisahkan ASSURANCE dan POINT karena bukan uang tunai
Piutang berjalan/sales, saring amounts.outstanding lebih besar dari nol dan status bukan CANCELED
Klaim BPJS atau asuransi/sales/payments?type=ASSURANCE, atau amounts.insurance_total di objek sale
Barang terlaris/sales, kelompokkan items[].quantity per item_id
Nilai persediaan/warehouse/stocks dikalikan sell_price dari /warehouse/items (HPP tidak tersedia di v1)
Kartu stok satu barang/warehouse/stock-movements?item_id=..., urut occurred_at
Sinkron pelanggan ke CRM/members dengan updated_since; normalkan telephone_number di sisi Anda

Contoh: Google Apps Script ke Google Sheets

Simpan key lewat Project Settings → Script Properties dengan nama LENSIRO_KEY, jangan ditulis langsung di kode.

const BASE = 'https://app-anda.lensiro.com/api/v1';

function tarikPenjualan() {
  const props = PropertiesService.getScriptProperties();
  const key = props.getProperty('LENSIRO_KEY');
  const since = props.getProperty('last_updated_at') || '2026-01-01T00:00:00Z';
  const sheet = SpreadsheetApp.getActive().getSheetByName('Penjualan');

  let cursor = null;
  let maxUpdated = since;
  const rows = [];

  do {
    let url = BASE + '/sales?include_deleted=true&limit=200'
            + '&updated_since=' + encodeURIComponent(since);
    if (cursor) url += '&cursor=' + encodeURIComponent(cursor);

    const res = UrlFetchApp.fetch(url, {
      headers: { Authorization: 'Bearer ' + key },
      muteHttpExceptions: true,
    });

    if (res.getResponseCode() === 429) {
      Utilities.sleep(1000 * (Number(res.getHeaders()['Retry-After']) || 5));
      continue;
    }
    if (res.getResponseCode() !== 200) {
      throw new Error(res.getContentText());
    }

    const body = JSON.parse(res.getContentText());
    body.data.forEach(function (s) {
      if (s.updated_at > maxUpdated) maxUpdated = s.updated_at;
      rows.push([
        s.id,
        s.sales_order_number,
        s.invoice_number,
        s.transaction_date,
        s.branch.name,
        s.status,
        s.amounts.net_total,
        s.amounts.paid_total,
        s.deleted_at ? 'DIHAPUS' : '',
      ]);
    });

    cursor = body.pagination.next_cursor;
  } while (cursor);

  if (rows.length) {
    sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length)
         .setValues(rows);
  }
  props.setProperty('last_updated_at', maxUpdated);
}

Contoh di atas menambahkan baris, belum meng-upsert. Untuk pemakaian sungguhan, cari dulu baris dengan id yang sama lalu timpa — kalau tidak, transaksi yang berubah akan muncul dua kali di sheet Anda.

Jadwalkan lewat Triggers → Add Trigger → Time-driven → Hour timer.

Contoh: Python

import os
import time
import requests

BASE = "https://app-anda.lensiro.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['LENSIRO_API_KEY']}"}


def tarik(path, **params):
    """Generator yang menghasilkan seluruh baris dari sebuah endpoint list."""
    cursor = None
    while True:
        query = {**params, "limit": 200}
        if cursor:
            query["cursor"] = cursor

        res = requests.get(BASE + path, headers=HEADERS, params=query, timeout=30)

        if res.status_code == 429:
            time.sleep(int(res.headers.get("Retry-After", 5)))
            continue
        res.raise_for_status()

        body = res.json()
        yield from body["data"]

        if not body["pagination"]["has_more"]:
            return
        cursor = body["pagination"]["next_cursor"]


terbaru = "2026-08-01T00:00:00Z"
for sale in tarik("/sales", updated_since=terbaru, include_deleted="true"):
    if sale["deleted_at"]:
        hapus(sale["id"])          # ganti dengan penyimpanan Anda
    else:
        upsert(sale)               # ganti dengan penyimpanan Anda
    terbaru = max(terbaru, sale["updated_at"])

simpan_checkpoint(terbaru)

Contoh: Node.js

const BASE = 'https://app-anda.lensiro.com/api/v1';
const KEY = process.env.LENSIRO_API_KEY;

async function* tarik(path, params = {}) {
  let cursor = null;
  for (;;) {
    const query = new URLSearchParams({ ...params, limit: '200' });
    if (cursor) query.set('cursor', cursor);

    const res = await fetch(BASE + path + '?' + query, {
      headers: { Authorization: 'Bearer ' + KEY },
    });

    if (res.status === 429) {
      const wait = Number(res.headers.get('Retry-After') || 5);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    if (!res.ok) throw new Error(await res.text());

    const body = await res.json();
    yield* body.data;

    if (!body.pagination.has_more) return;
    cursor = body.pagination.next_cursor;
  }
}

for await (const sale of tarik('/sales', { updated_since: '2026-08-01T00:00:00Z' })) {
  console.log(sale.id, sale.sales_order_number, sale.amounts.net_total);
}

Memakai API ini dengan agent AI

API ini cocok dijadikan alat (tool) untuk agent AI internal — misalnya asisten yang bisa ditanyai "omzet cabang Bandung minggu lalu berapa?". Beberapa saran:

  • Berikan halaman Referensi Endpoint sebagai konteks; strukturnya memang dibuat ringkas untuk dibaca mesin.
  • Batasi agent hanya ke endpoint list dengan limit kecil, supaya satu pertanyaan tidak menghabiskan kuota rate limit.
  • Ingatkan agent bahwa semua tanggal UTC dan harus dikonversi ke WIB sebelum dijawab ke pengguna.
  • Karena key memberi akses baca penuh, jangan tempelkan key ke agent yang bisa diakses pelanggan.

Checklist produksi

  • Key disimpan sebagai secret, dan khusus untuk integrasi ini saja.
  • Loop paginasi berhenti berdasarkan has_more.
  • Semua filter dibawa ulang di setiap halaman berikutnya.
  • Penyimpanan melakukan upsert berdasarkan id, termasuk id berbentuk string untuk pergerakan stok.
  • include_deleted=true dipakai, dan deleted_at ditindaklanjuti.
  • 429 ditangani dengan Retry-After; 500 dicoba ulang dengan jeda bertingkat.
  • Checkpoint updated_at hanya disimpan setelah seluruh halaman selesai.
  • Tanggal dikonversi dari UTC ke WIB sebelum dikelompokkan per hari.
  • Field JSON yang tidak dikenal diabaikan, bukan menyebabkan error.

Solusi lengkap untuk toko optik modern dengan teknologi terdepan dan dukungan terbaik.

Fitur

  • Penjualan Optik
  • Faset & QC
  • Inventory & Gudang
  • Membership & After-Service
  • Multi-Cabang & Roles
  • Financial Statement

Solusi

Perusahaan

©Lensiro. All Rights Reserved.

Kebijakan Privasi