Langsung ke konten
Inolab

API dan webhook

API Inolab dipakai untuk menghubungkan pelanggan, tagihan, dan pembayaran di Inolab dengan sistem lain, misalnya toko online, aplikasi kasir, atau software akuntansi. Semua permintaan dan respons berformat JSON melalui HTTPS.

Base URL: https://app.inolab.id/api/v1 · Spesifikasi OpenAPI 3.1: openapi.yaml

Autentikasi

Buat kunci API di aplikasi, di Pengaturan › Integrasi. Hanya pemilik dan admin usaha yang dapat membuatnya. Kunci hanya ditampilkan sekali; simpan di tempat yang aman dan jangan taruh di kode yang berjalan di peramban atau aplikasi seluler.

Kirim kunci di header Authorization setiap permintaan:

curl "https://app.inolab.id/api/v1/invoices?status=overdue" \
  -H "Authorization: Bearer 12|inolab_xxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json"
  • Kunci berlaku untuk satu usaha, yaitu usaha tempat kunci dibuat. Header X-Tenant-Id tidak diperlukan.
  • Kunci bertindak atas nama pembuatnya. Izinnya dibatasi cakupan yang dipilih dan tidak pernah melebihi peran pembuatnya. Bila pembuatnya keluar dari usaha, kunci berhenti bekerja.
  • Kunci dapat dicabut kapan saja dari halaman yang sama.

Izin kunci API

CakupanMengizinkan
customers:readLihat pelanggan
customers:writeTambah, ubah, dan hapus pelanggan
invoices:readLihat tagihan dan ringkasan
invoices:writeBuat, ubah, terbitkan, dan batalkan tagihan
payments:writeCatat dan hapus pembayaran tagihan
expenses:readLihat biaya
expenses:writeCatat, ubah, dan hapus biaya

Endpoint akun, langganan, dan notifikasi hanya untuk aplikasi Inolab dan tidak dapat diakses dengan kunci API.

Endpoint

MetodePathCakupanKeterangan
GET /customers customers:read Daftar pelanggan; ?q=, ?type=company|individual, ?per_page= (maks. 100)
GET /customers/{id} customers:read Satu pelanggan
POST /customers customers:write Tambah pelanggan
PUT /customers/{id} customers:write Ubah pelanggan
DELETE /customers/{id} customers:write Hapus pelanggan tanpa tagihan terbuka
GET /invoices invoices:read Daftar tagihan; ?status=draft|open|overdue|paid|void, ?q=, ?customer_id=
GET /invoices/{id} invoices:read Satu tagihan beserta item dan pembayaran
GET /invoices/{id}/pdf invoices:read PDF tagihan (application/pdf)
GET /dashboard invoices:read Angka piutang dan tagihan lewat jatuh tempo
POST /invoices invoices:write Buat draf tagihan; total dihitung server
PUT /invoices/{id} invoices:write Ubah draf
DELETE /invoices/{id} invoices:write Hapus draf
POST /invoices/{id}/send invoices:write Terbitkan; email_customer=true untuk mengirim email
POST /invoices/{id}/void invoices:write Batalkan tagihan tanpa pembayaran; reason opsional
POST /invoices/{id}/payments payments:write Catat pembayaran: amount, paid_on, method, reference, notes
DELETE /invoices/{id}/payments/{paymentId} payments:write Hapus catatan pembayaran
GET /expenses expenses:read Daftar biaya; ?from=, ?to=, ?category=, ?q=
GET /expenses/{id} expenses:read Satu biaya
POST /expenses expenses:write Catat biaya: spent_on, category, description, amount, tax_amount, method
PUT /expenses/{id} expenses:write Ubah biaya
DELETE /expenses/{id} expenses:write Hapus biaya

Contoh menambah pelanggan:

curl -X POST "https://app.inolab.id/api/v1/customers" \
  -H "Authorization: Bearer 12|inolab_xxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"type": "company", "name": "PT Maju Bersama", "email": "keuangan@majubersama.co.id"}'
  • Nilai uang dalam Rupiah utuh tanpa desimal, misalnya 5550000.
  • Tanggal berformat YYYY-MM-DD; waktu berformat ISO 8601 dengan zona waktu.
  • Tarif pajak tagihan: 0, 11, atau 12 persen. Nomor, subtotal, pajak, dan total dihitung server.
  • Metode pembayaran: bank_transfer, qris, ewallet, cash, card, other.

Respons dan galat

Setiap respons memakai amplop yang sama. Daftar berhalaman menyertakan meta.pagination.

{
  "success": true,
  "message": "Permintaan berhasil.",
  "data": [ ... ],
  "meta": { "pagination": { "current_page": 1, "per_page": 25, "total": 42, "last_page": 2 } }
}
{
  "success": false,
  "message": "Kunci API ini tidak memiliki izin untuk tindakan ini.",
  "code": "missing_scope"
}
StatusArti
401Kunci tidak ada, salah, sudah dicabut, atau kedaluwarsa.
403Cakupan kunci tidak mencukupi (code: missing_scope) atau peran pembuat kunci tidak mengizinkan.
404Data tidak ada atau milik usaha lain.
422Data tidak valid; rinciannya di errors per kolom.
429Terlalu banyak permintaan. Batasnya 120 permintaan per menit per pengguna; tunggu sesuai header Retry-After.

Kirim Accept-Language: en untuk pesan dalam bahasa Inggris. Bawaannya bahasa Indonesia.

Webhook

Webhook memberi tahu sistem Anda saat data berubah, sehingga Anda tidak perlu memeriksa API berulang kali. Tambahkan alamat penerima di Pengaturan › Integrasi dan pilih event yang ingin diterima. Alamat harus HTTPS publik di port 443; alamat jaringan internal ditolak.

EventDikirim saat
customer.createdPelanggan ditambahkan
customer.updatedData pelanggan diubah
customer.deletedPelanggan dihapus
invoice.createdDraf tagihan dibuat
invoice.updatedDraf tagihan diubah
invoice.sentTagihan diterbitkan
invoice.paidTagihan lunas
invoice.voidedTagihan dibatalkan
invoice.deletedDraf tagihan dihapus
invoice.payment_recordedPembayaran tagihan dicatat
invoice.payment_deletedCatatan pembayaran dihapus

Setiap event dikirim sebagai POST berisi JSON:

POST /webhooks/inolab HTTP/1.1
Content-Type: application/json
User-Agent: Inolab-Webhooks/1.0
Inolab-Event: invoice.paid
Inolab-Event-Id: evt_01k7c3m0q4f9w2v8t6r5y1x3za
Inolab-Delivery: 01k7c3m0r8a2b4c6d8e0f2g4h6
Inolab-Signature: t=1791520200,v1=5f2b9c…
{
  "id": "evt_01k7c3m0q4f9w2v8t6r5y1x3za",
  "type": "invoice.paid",
  "created_at": "2026-10-09T14:30:00+07:00",
  "tenant_id": "01k6xq5a9d3m7p2r4t6v8x0z2b",
  "data": {
    "id": "01k7a0b2c4d6e8f0g2h4j6k8m0",
    "number": "INV/2026/0042",
    "status": "paid",
    "customer": { "id": "01k6…", "name": "PT Maju Bersama", "email": "keuangan@majubersama.co.id" },
    "issue_date": "2026-10-01",
    "due_date": "2026-10-15",
    "currency": "IDR",
    "subtotal": 5000000,
    "tax_rate": 11,
    "tax_amount": 550000,
    "total": 5550000,
    "amount_paid": 5550000,
    "balance_due": 0,
    "items": [ { "description": "Jasa desain kemasan", "quantity": 1, "unit_price": 5000000, "amount": 5000000 } ],
    "payments": [ ... ],
    "paid_at": "2026-10-09T14:30:00+07:00"
  }
}
  • data berisi objek dengan bentuk yang sama seperti respons API. Event pembayaran berisi data.payment dan data.invoice.
  • Balas dengan status 2xx dalam 10 detik. Status lain, timeout, atau pengalihan (redirect) dianggap gagal.
  • Kiriman yang gagal dicoba ulang hingga 5 kali dalam sekitar 9 jam, lalu ditandai gagal. Riwayatnya dapat dilihat dan dikirim ulang dari aplikasi.
  • Satu event bisa diterima lebih dari sekali dan urutannya tidak dijamin. Pakai id event (Inolab-Event-Id) untuk mengabaikan duplikat.
  • Tombol Kirim uji coba mengirim event ping.

Memeriksa tanda tangan

Header Inolab-Signature berisi waktu kirim (t, detik Unix) dan v1, yaitu HMAC-SHA256 dari t, titik, lalu body mentah, dengan rahasia penandatangan webhook sebagai kunci. Tolak permintaan yang tanda tangannya tidak cocok atau waktunya lebih dari 5 menit dari sekarang.

// $secret: the signing secret shown when the webhook was created.
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_INOLAB_SIGNATURE'] ?? ''), $signature);

$expected = hash_hmac('sha256', ($signature['t'] ?? '').'.'.$body, $secret);
$fresh = abs(time() - (int) ($signature['t'] ?? 0)) <= 300;

if (! $fresh || ! hash_equals($expected, $signature['v1'] ?? '')) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// Use $event['id'] to ignore an event you have already handled.
http_response_code(200);
import crypto from 'node:crypto';

// rawBody: the request body exactly as received (a string, not parsed JSON).
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;

  return fresh
    && typeof parts.v1 === 'string'
    && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Rahasia penandatangan ditampilkan sekali saat webhook dibuat. Bila bocor, buat rahasia baru dari halaman webhook; rahasia lama langsung tidak berlaku.

Yang belum tersedia

  • OAuth untuk aplikasi pihak ketiga dan SDK resmi. Untuk saat ini, gunakan kunci API per usaha.
  • Endpoint laporan (umur piutang, pendapatan); datanya dapat diunduh sebagai CSV dari aplikasi.

Pertanyaan teknis: support@inolab.id.