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://api.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://api.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-Idtidak 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
| Cakupan | Mengizinkan |
|---|---|
customers:read | Lihat pelanggan |
customers:write | Tambah, ubah, dan hapus pelanggan |
invoices:read | Lihat tagihan, tagihan berulang, dan ringkasan |
invoices:write | Buat, ubah, terbitkan, dan batalkan tagihan; kelola tagihan berulang |
payments:write | Catat dan hapus pembayaran tagihan |
payments:refund | Kembalikan dana pembayaran online ke pelanggan lewat akun Xendit usaha |
quotes:read | Lihat penawaran harga |
quotes:write | Buat, kirim, dan catat jawaban penawaran; jadikan tagihan |
expenses:read | Lihat biaya |
expenses:write | Catat, ubah, dan hapus biaya |
projects:read | Lihat proyek dan ringkasannya |
projects:write | Buat, ubah, dan hapus proyek |
products:read | Lihat katalog produk dan jasa |
products:write | Tambah, ubah, dan hapus isi katalog |
deals:read | Lihat peluang |
deals:write | Buat, ubah, pindah tahap, dan hapus peluang |
activities:read | Lihat aktivitas pelanggan |
activities:write | Catat aktivitas dan selesaikan tindak lanjut |
reports:read | Lihat laporan keuangan |
agents:read | Lihat agen dan hasil kerjanya |
agents:run | Jalankan dan batalkan agen (persetujuan hanya oleh orang, di web atau aplikasi) |
usage:read | Lihat pemakaian AI dan biayanya |
audit_log:read | Baca log aktivitas |
Endpoint akun, langganan, dan notifikasi hanya untuk aplikasi Inolab dan tidak dapat diakses dengan kunci API.
Endpoint
| Metode | Path | Cakupan | Keterangan |
|---|---|---|---|
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 |
/recurring-invoices |
invoices:read |
Jadwal tagihan berulang; ?status=active|paused|ended, ?customer_id= |
GET |
/recurring-invoices/{id} |
invoices:read |
Satu jadwal beserta tiga tanggal berikutnya |
POST |
/recurring-invoices |
invoices:write |
Buat jadwal: title, customer_id, interval=month|year, interval_count, next_issue_on, due_days, send_automatically, items |
PUT |
/recurring-invoices/{id} |
invoices:write |
Ubah jadwal; tagihan yang sudah dibuat tidak berubah |
POST |
/recurring-invoices/{id}/pause |
invoices:write |
Jeda; /resume untuk melanjutkan |
DELETE |
/recurring-invoices/{id} |
invoices:write |
Hapus jadwal |
GET |
/quotes |
quotes:read |
Daftar penawaran; ?status=draft|sent|expired|accepted|declined, ?customer_id=, ?deal_id= |
GET |
/quotes/{id} |
quotes:read |
Satu penawaran beserta item |
GET |
/quotes/{id}/pdf |
quotes:read |
PDF penawaran (application/pdf) |
POST |
/quotes |
quotes:write |
Buat draf penawaran: customer_id, issue_date, valid_until, items |
PUT |
/quotes/{id} |
quotes:write |
Ubah draf penawaran |
POST |
/quotes/{id}/send |
quotes:write |
Kirim; email_customer=true untuk mengirim email dengan PDF |
POST |
/quotes/{id}/accept |
quotes:write |
Catat diterima; /decline dengan reason untuk ditolak |
POST |
/quotes/{id}/convert |
quotes:write |
Jadikan draf tagihan sekali; perlu juga invoices:write |
DELETE |
/quotes/{id} |
quotes:write |
Hapus draf penawaran |
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 |
GET |
/projects |
projects:read |
Daftar proyek; ?status=open|all|..., ?customer_id=, ?q= |
GET |
/projects/{id} |
projects:read |
Satu proyek beserta ringkasan tagihan, biaya, dan margin |
POST |
/projects |
projects:write |
Buat proyek: customer_id, name, status, contract_value |
PUT |
/projects/{id} |
projects:write |
Ubah proyek |
DELETE |
/projects/{id} |
projects:write |
Hapus proyek tanpa tagihan atau biaya |
GET |
/products |
products:read |
Katalog produk dan jasa; ?type=service|goods, ?q= |
GET |
/products/{id} |
products:read |
Satu isi katalog |
POST |
/products |
products:write |
Tambah ke katalog: type, name, sku, unit, unit_price |
PUT |
/products/{id} |
products:write |
Ubah isi katalog; tagihan lama tidak berubah |
DELETE |
/products/{id} |
products:write |
Hapus dari katalog |
GET |
/deals |
deals:read |
Daftar peluang; ?stage=open|won|..., ?customer_id=, ?owner_id= |
GET |
/deals/{id} |
deals:read |
Satu peluang |
POST |
/deals |
deals:write |
Buat peluang: customer_id, title, stage, value |
PUT |
/deals/{id} |
deals:write |
Ubah peluang |
POST |
/deals/{id}/stage |
deals:write |
Pindah tahap; lost_reason bila kalah |
DELETE |
/deals/{id} |
deals:write |
Hapus peluang |
GET |
/activities |
activities:read |
Daftar aktivitas; ?customer_id=, ?deal_id=, ?follow_up=due |
POST |
/customers/{id}/activities |
activities:write |
Catat aktivitas: type, happened_on, body, follow_up_on |
POST |
/activities/{id}/complete |
activities:write |
Tandai tindak lanjut selesai |
DELETE |
/activities/{id} |
activities:write |
Hapus aktivitas |
GET |
/reports/aging |
reports:read |
Umur piutang per pelanggan; ?as_of= |
GET |
/reports/revenue |
reports:read |
Ditagihkan dan diterima per bulan; ?year= |
GET |
/reports/cash-flow |
reports:read |
Uang masuk dan keluar per bulan; ?year= |
GET |
/reports/profit-and-loss |
reports:read |
Laba rugi sederhana; ?year=, ?month= |
GET |
/reports/vat |
reports:read |
PPN keluaran dan masukan per bulan; ?year= |
GET |
/reports/quotes |
reports:read |
Penawaran dikirim dan diterima per bulan, tingkat diterima; ?year= |
GET |
/reports/recurring-revenue |
reports:read |
Pendapatan berulang setara per bulan dan tagihan 90 hari ke depan |
GET |
/agents |
agents:read |
Agen yang boleh dijalankan, input-nya, dan apakah AI siap |
POST |
/agents/{agent}/runs |
agents:run |
Jalankan agen; header Idempotency-Key agar aman diulang |
GET |
/agent-runs |
agents:read |
Daftar pekerjaan agen; ?status=, ?agent= |
GET |
/agent-runs/{id} |
agents:read |
Satu pekerjaan agen beserta riwayat status; persetujuan memberi tautan ke web |
POST |
/agent-runs/{id}/cancel |
agents:run |
Batalkan pekerjaan agen |
Contoh menambah pelanggan:
curl -X POST "https://api.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, atau12persen. 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"
}
| Status | Arti |
|---|---|
401 | Kunci tidak ada, salah, sudah dicabut, atau kedaluwarsa. |
403 | Cakupan kunci tidak mencukupi (code: missing_scope) atau peran pembuat kunci tidak mengizinkan. |
404 | Data tidak ada atau milik usaha lain. |
422 | Data tidak valid; rinciannya di errors per kolom. |
429 | Terlalu 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.
| Event | Dikirim saat |
|---|---|
customer.created | Pelanggan ditambahkan |
customer.updated | Data pelanggan diubah |
customer.deleted | Pelanggan dihapus |
invoice.created | Draf tagihan dibuat |
invoice.updated | Draf tagihan diubah |
invoice.sent | Tagihan diterbitkan |
invoice.paid | Tagihan lunas |
invoice.voided | Tagihan dibatalkan |
invoice.deleted | Draf tagihan dihapus |
invoice.shipped | Pengiriman barang dicatat |
invoice.payment_recorded | Pembayaran tagihan dicatat |
invoice.payment_deleted | Catatan pembayaran dihapus |
invoice.payment_refunded | Sebagian pembayaran dikembalikan |
quote.created | Draf penawaran dibuat |
quote.updated | Draf penawaran diubah |
quote.sent | Penawaran dikirim |
quote.accepted | Penawaran diterima pelanggan |
quote.declined | Penawaran ditolak pelanggan |
quote.deleted | Draf penawaran dihapus |
deal.created | Peluang dibuat |
deal.updated | Peluang diubah |
deal.stage_changed | Tahap peluang berubah (termasuk menang atau kalah) |
deal.deleted | Peluang dihapus |
project.created | Proyek dibuat |
project.updated | Proyek diubah |
project.deleted | Proyek 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": [ { "product_id": null, "description": "Jasa desain kemasan", "quantity": 1, "unit": "paket", "unit_price": 5000000, "amount": 5000000 } ],
"payments": [ ... ],
"paid_at": "2026-10-09T14:30:00+07:00"
}
}
databerisi objek dengan bentuk yang sama seperti respons API. Event pembayaran berisidata.paymentdandata.invoice.- Balas dengan status
2xxdalam 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
idevent (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.
OAuth untuk aplikasi pihak ketiga
Bila aplikasi Anda dipakai banyak usaha, minta izin lewat OAuth 2.0 (authorization code, PKCE S256 dianjurkan) alih-alih meminta mereka menyalin kunci API. Daftarkan aplikasi di Pengaturan › Integrasi › Aplikasi OAuth untuk mendapat client ID dan client secret.
- Arahkan pengguna ke
https://app.inolab.id/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=customers:read invoices:read&state=…&code_challenge=…&code_challenge_method=S256. - Pemilik atau admin memilih usaha dan menyetujui izin. Inolab kembali ke
redirect_uridengancodedanstate, atauerror=access_denied. - Dari server Anda, tukar kode (berlaku 10 menit, sekali pakai) di
POST https://app.inolab.id/oauth/tokendengangrant_type=authorization_code,code,redirect_uri,code_verifier, dan kredensial klien (HTTP Basic atauclient_id/client_secretdi body). - Respons berisi
access_token(Bearer) yang berlaku 365 hari untuk satu usaha dengan izin yang disetujui, dipakai persis seperti kunci API. Usaha dapat mencabutnya kapan saja.
Yang belum tersedia
- SDK resmi dan refresh token OAuth. Gunakan openapi.yaml untuk membuat klien.
Pertanyaan teknis: support@inolab.id.
