Bỏ qua đến nội dung
Inolab

API và webhook

The Inolab API connects the customers, invoices and payments in Inolab with other systems, such as an online store, a point-of-sale app or accounting software. All requests and responses are JSON over HTTPS.

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

Authentication

Create an API key in the app under Settings › Integrations. Only business owners and admins can create keys. A key is shown once; store it somewhere safe and never put it in code that runs in a browser or mobile app.

Send the key in the Authorization header of every request:

curl "https://app.inolab.id/api/v1/invoices?status=overdue" \
  -H "Authorization: Bearer 12|inolab_xxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json"
  • A key works for one business, the one it was created in. The X-Tenant-Id header is not needed.
  • A key acts on behalf of the person who created it. It is limited to the scopes chosen and never exceeds that person's role. If they leave the business, the key stops working.
  • Keys can be revoked at any time from the same page.

API key scopes

ScopeAllows
customers:readXem khách hàng
customers:writeThêm, sửa và xóa khách hàng
invoices:readXem hóa đơn, hóa đơn định kỳ và tóm tắt
invoices:writeTạo, sửa, phát hành và hủy hóa đơn; quản lý hóa đơn định kỳ
payments:writeGhi nhận và xóa thanh toán hóa đơn
quotes:readXem báo giá
quotes:writeTạo, gửi và ghi nhận phản hồi báo giá; chuyển thành hóa đơn
expenses:readXem chi phí
expenses:writeGhi, sửa và xóa chi phí
projects:readXem dự án và tóm tắt
projects:writeTạo, sửa và xóa dự án
products:readXem danh mục sản phẩm và dịch vụ
products:writeThêm, sửa và gỡ các mục danh mục
deals:readXem cơ hội
deals:writeTạo, sửa, chuyển giai đoạn và xóa cơ hội
activities:readXem hoạt động khách hàng
activities:writeGhi nhận hoạt động và hoàn tất việc theo dõi
reports:readXem báo cáo tài chính
agents:readXem tác tử và các lượt chạy
agents:runChạy và hủy tác tử (chỉ con người mới phê duyệt, trên web hoặc ứng dụng)
usage:readXem mức dùng AI và chi phí
audit_log:readXem nhật ký hoạt động

Account, subscription and notification endpoints are for the Inolab apps only and cannot be used with an API key.

Endpoints

MethodPathScopeDescription
GET /customers customers:read Danh sách khách hàng; ?q=, ?type=company|individual, ?per_page= (tối đa 100)
GET /customers/{id} customers:read Một khách hàng
POST /customers customers:write Thêm khách hàng
PUT /customers/{id} customers:write Sửa khách hàng
DELETE /customers/{id} customers:write Xóa khách hàng không có hóa đơn đang mở
GET /invoices invoices:read Danh sách hóa đơn; ?status=draft|open|overdue|paid|void, ?q=, ?customer_id=
GET /invoices/{id} invoices:read Một hóa đơn kèm các mặt hàng và thanh toán
GET /invoices/{id}/pdf invoices:read PDF hóa đơn (application/pdf)
GET /dashboard invoices:read Số liệu công nợ và hóa đơn quá hạn
POST /invoices invoices:write Tạo bản nháp hóa đơn; tổng tiền do máy chủ tính
PUT /invoices/{id} invoices:write Sửa một bản nháp
DELETE /invoices/{id} invoices:write Xóa bản nháp
POST /invoices/{id}/send invoices:write Phát hành; email_customer=true để gửi email
POST /invoices/{id}/void invoices:write Hủy hóa đơn chưa có thanh toán; reason không bắt buộc
POST /invoices/{id}/payments payments:write Ghi nhận thanh toán: amount, paid_on, method, reference, notes
DELETE /invoices/{id}/payments/{paymentId} payments:write Xóa bản ghi thanh toán
GET /recurring-invoices invoices:read Lịch hóa đơn định kỳ; ?status=active|paused|ended, ?customer_id=
GET /recurring-invoices/{id} invoices:read Một lịch kèm ba ngày tiếp theo
POST /recurring-invoices invoices:write Tạo lịch: title, customer_id, interval=month|year, interval_count, next_issue_on, due_days, send_automatically, items
PUT /recurring-invoices/{id} invoices:write Sửa một lịch; các hóa đơn đã tạo không thay đổi
POST /recurring-invoices/{id}/pause invoices:write Tạm dừng; /resume để tiếp tục
DELETE /recurring-invoices/{id} invoices:write Xóa lịch
GET /quotes quotes:read Báo giá; ?status=draft|sent|expired|accepted|declined, ?customer_id=, ?deal_id=
GET /quotes/{id} quotes:read Một báo giá kèm các mặt hàng
GET /quotes/{id}/pdf quotes:read PDF báo giá (application/pdf)
POST /quotes quotes:write Tạo bản nháp báo giá: customer_id, issue_date, valid_until, items
PUT /quotes/{id} quotes:write Sửa một bản nháp báo giá
POST /quotes/{id}/send quotes:write Gửi; email_customer=true để gửi email kèm PDF
POST /quotes/{id}/accept quotes:write Ghi nhận chấp nhận; /decline kèm reason để ghi nhận từ chối
POST /quotes/{id}/convert quotes:write Chuyển thành bản nháp hóa đơn một lần; cũng cần invoices:write
DELETE /quotes/{id} quotes:write Xóa bản nháp báo giá
GET /expenses expenses:read Danh sách chi phí; ?from=, ?to=, ?category=, ?q=
GET /expenses/{id} expenses:read Một chi phí
POST /expenses expenses:write Ghi chi phí: spent_on, category, description, amount, tax_amount, method
PUT /expenses/{id} expenses:write Sửa chi phí
DELETE /expenses/{id} expenses:write Xóa chi phí
GET /projects projects:read Danh sách dự án; ?status=open|all|..., ?customer_id=, ?q=
GET /projects/{id} projects:read Một dự án kèm tóm tắt số đã lập hóa đơn, chi phí và biên lợi nhuận
POST /projects projects:write Tạo dự án: customer_id, name, status, contract_value
PUT /projects/{id} projects:write Sửa dự án
DELETE /projects/{id} projects:write Xóa dự án không có hóa đơn hoặc chi phí
GET /products products:read Danh mục sản phẩm và dịch vụ; ?type=service|goods, ?q=
GET /products/{id} products:read Một mục danh mục
POST /products products:write Thêm vào danh mục: type, name, sku, unit, unit_price
PUT /products/{id} products:write Sửa một mục danh mục; các hóa đơn hiện có không thay đổi
DELETE /products/{id} products:write Gỡ khỏi danh mục
GET /deals deals:read Danh sách cơ hội; ?stage=open|won|..., ?customer_id=, ?owner_id=
GET /deals/{id} deals:read Một cơ hội
POST /deals deals:write Tạo cơ hội: customer_id, title, stage, value
PUT /deals/{id} deals:write Sửa cơ hội
POST /deals/{id}/stage deals:write Chuyển giai đoạn; lost_reason khi thua
DELETE /deals/{id} deals:write Xóa cơ hội
GET /activities activities:read Danh sách hoạt động; ?customer_id=, ?deal_id=, ?follow_up=due
POST /customers/{id}/activities activities:write Ghi nhận hoạt động: type, happened_on, body, follow_up_on
POST /activities/{id}/complete activities:write Đánh dấu việc theo dõi là hoàn tất
DELETE /activities/{id} activities:write Xóa hoạt động
GET /reports/aging reports:read Tuổi nợ phải thu theo khách hàng; ?as_of=
GET /reports/revenue reports:read Đã lập hóa đơn và đã thu theo tháng; ?year=
GET /reports/cash-flow reports:read Tiền vào và ra theo tháng; ?year=
GET /reports/profit-and-loss reports:read Lãi lỗ đơn giản; ?year=, ?month=
GET /reports/vat reports:read VAT đầu ra và đầu vào theo tháng; ?year=
GET /reports/quotes reports:read Báo giá đã gửi và được chấp nhận theo tháng, tỷ lệ chấp nhận; ?year=
GET /reports/recurring-revenue reports:read Doanh thu định kỳ quy đổi theo tháng và hóa đơn trong 90 ngày tới
GET /agents agents:read Các tác tử bạn được chạy, đầu vào của chúng và AI đã sẵn sàng hay chưa
POST /agents/{agent}/runs agents:run Chạy tác tử; gửi header Idempotency-Key để thử lại an toàn
GET /agent-runs agents:read Lượt chạy tác tử; ?status=, ?agent=
GET /agent-runs/{id} agents:read Một lượt chạy tác tử kèm lịch sử trạng thái; phê duyệt liên kết tới ứng dụng web
POST /agent-runs/{id}/cancel agents:run Hủy một lượt chạy tác tử

Adding a customer:

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"}'
  • Money is whole Rupiah without decimals, for example 5550000.
  • Dates use YYYY-MM-DD; times use ISO 8601 with a time zone.
  • Invoice tax rates: 0, 11 or 12 percent. Numbers, subtotal, tax and total are calculated by the server.
  • Payment methods: bank_transfer, qris, ewallet, cash, card, other.

Responses and errors

Every response uses the same envelope. Paginated lists include 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"
}
StatusMeaning
401The key is missing, wrong, revoked or expired.
403The key's scopes are not enough (code: missing_scope), or the key creator's role does not allow it.
404The record does not exist or belongs to another business.
422Invalid data; details per field in errors.
429Too many requests. The limit is 120 requests a minute per user; wait as long as the Retry-After header says.

Send Accept-Language: en for messages in English. The default is Indonesian.

Webhooks

Webhooks tell your system when data changes, so you do not have to poll the API. Add a receiving URL under Settings › Integrations and choose the events to receive. The URL must be public HTTPS on port 443; internal network addresses are refused.

EventSent when
customer.createdĐã thêm khách hàng
customer.updatedĐã cập nhật khách hàng
customer.deletedĐã xóa khách hàng
invoice.createdĐã tạo bản nháp hóa đơn
invoice.updatedĐã cập nhật bản nháp hóa đơn
invoice.sentĐã phát hành hóa đơn
invoice.paidHóa đơn đã thanh toán
invoice.voidedĐã hủy hóa đơn
invoice.deletedĐã xóa bản nháp hóa đơn
invoice.payment_recordedĐã ghi nhận thanh toán hóa đơn
invoice.payment_deletedĐã xóa bản ghi thanh toán
quote.createdĐã tạo bản nháp báo giá
quote.updatedĐã sửa bản nháp báo giá
quote.sentĐã gửi báo giá
quote.acceptedKhách hàng đã chấp nhận báo giá
quote.declinedKhách hàng đã từ chối báo giá
quote.deletedĐã xóa bản nháp báo giá
deal.createdĐã tạo cơ hội
deal.updatedĐã sửa cơ hội
deal.stage_changedGiai đoạn cơ hội thay đổi (kể cả thắng hoặc thua)
deal.deletedĐã xóa cơ hội
project.createdĐã tạo dự án
project.updatedĐã sửa dự án
project.deletedĐã xóa dự án

Each event is sent as a JSON POST:

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"
  }
}
  • data holds an object shaped like the API response. Payment events contain data.payment and data.invoice.
  • Reply with a 2xx status within 10 seconds. Any other status, a timeout or a redirect counts as a failure.
  • Failed deliveries are retried up to 5 times over about 9 hours, then marked failed. You can see and resend them in the app.
  • An event may arrive more than once and order is not guaranteed. Use the event id (Inolab-Event-Id) to ignore duplicates.
  • The Send test button sends a ping event.

Verifying signatures

The Inolab-Signature header holds the send time (t, Unix seconds) and v1, an HMAC-SHA256 of t, a dot and the raw body, keyed with the webhook's signing secret. Reject requests whose signature does not match or whose time is more than 5 minutes away from now.

// $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));
}

The signing secret is shown once when the webhook is created. If it leaks, create a new secret from the webhook page; the old one stops working immediately.

OAuth for third-party apps

If your app is used by many businesses, ask for access through OAuth 2.0 (authorization code; PKCE S256 recommended) instead of asking them to copy an API key. Register the app under Settings › Integrations › OAuth apps to get a client ID and client secret.

  1. Send the person to 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.
  2. An owner or admin picks a business and approves the scopes. Inolab returns to redirect_uri with code and state, or error=access_denied.
  3. From your server, exchange the code (valid 10 minutes, single use) at POST https://app.inolab.id/oauth/token with grant_type=authorization_code, code, redirect_uri, code_verifier and the client credentials (HTTP Basic or client_id/client_secret in the body).
  4. The response holds an access_token (Bearer) valid for 365 days for one business with the approved scopes, used exactly like an API key. The business can revoke it at any time.

Not available yet

  • Official SDKs and OAuth refresh tokens. Use openapi.yaml to generate a client.

Technical questions: support@inolab.id.