Lumaktaw sa content
Inolab

API at 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:readTingnan ang mga customer
customers:writeMagdagdag, mag-edit at magbura ng mga customer
invoices:readTingnan ang mga invoice, recurring invoice at ang buod
invoices:writeGumawa, mag-edit, maglabas at mag-void ng mga invoice; pamahalaan ang mga recurring invoice
payments:writeMag-record at magbura ng mga bayad sa invoice
quotes:readTingnan ang mga quotation
quotes:writeGumawa, magpadala at mag-record ng sagot sa mga quotation; gawing invoice
expenses:readTingnan ang mga gastos
expenses:writeMag-record, mag-edit at magbura ng mga gastos
projects:readTingnan ang mga proyekto at ang buod nito
projects:writeGumawa, mag-edit at magbura ng mga proyekto
products:readTingnan ang catalog ng produkto at serbisyo
products:writeMagdagdag, mag-edit at mag-alis ng mga entry sa catalog
deals:readTingnan ang mga deal
deals:writeGumawa, mag-edit, maglipat ng stage at magbura ng mga deal
activities:readTingnan ang mga aktibidad ng customer
activities:writeMag-record ng aktibidad at tapusin ang mga follow-up
reports:readTingnan ang mga financial report
agents:readTingnan ang mga agent at ang kanilang mga run
agents:runMagsimula at magkansela ng mga agent (tao lang ang nag-aapruba, sa web o sa app)
usage:readTingnan ang paggamit ng AI at ang gastos nito
audit_log:readBasahin ang activity log

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 Listahan ng customer; ?q=, ?type=company|individual, ?per_page= (max. 100)
GET /customers/{id} customers:read Isang customer
POST /customers customers:write Magdagdag ng customer
PUT /customers/{id} customers:write I-edit ang customer
DELETE /customers/{id} customers:write Burahin ang customer na walang bukas na invoice
GET /invoices invoices:read Listahan ng invoice; ?status=draft|open|overdue|paid|void, ?q=, ?customer_id=
GET /invoices/{id} invoices:read Isang invoice kasama ang mga item at bayad nito
GET /invoices/{id}/pdf invoices:read PDF ng invoice (application/pdf)
GET /dashboard invoices:read Mga numero ng receivables at overdue na invoice
POST /invoices invoices:write Gumawa ng draft na invoice; server ang nagkukuwenta ng total
PUT /invoices/{id} invoices:write Mag-edit ng draft
DELETE /invoices/{id} invoices:write Burahin ang draft
POST /invoices/{id}/send invoices:write Ilabas; email_customer=true para ipadala ang email
POST /invoices/{id}/void invoices:write Kanselahin ang invoice na walang bayad; opsyonal ang reason
POST /invoices/{id}/payments payments:write Mag-record ng bayad: amount, paid_on, method, reference, notes
DELETE /invoices/{id}/payments/{paymentId} payments:write Burahin ang record ng bayad
GET /recurring-invoices invoices:read Mga schedule ng recurring invoice; ?status=active|paused|ended, ?customer_id=
GET /recurring-invoices/{id} invoices:read Isang schedule kasama ang susunod na tatlong petsa nito
POST /recurring-invoices invoices:write Gumawa ng schedule: title, customer_id, interval=month|year, interval_count, next_issue_on, due_days, send_automatically, items
PUT /recurring-invoices/{id} invoices:write Mag-edit ng schedule; hindi nagbabago ang mga invoice na nagawa na
POST /recurring-invoices/{id}/pause invoices:write I-pause; /resume para ituloy
DELETE /recurring-invoices/{id} invoices:write Burahin ang schedule
GET /quotes quotes:read Mga quotation; ?status=draft|sent|expired|accepted|declined, ?customer_id=, ?deal_id=
GET /quotes/{id} quotes:read Isang quotation kasama ang mga item nito
GET /quotes/{id}/pdf quotes:read PDF ng quotation (application/pdf)
POST /quotes quotes:write Gumawa ng draft na quotation: customer_id, issue_date, valid_until, items
PUT /quotes/{id} quotes:write Mag-edit ng draft na quotation
POST /quotes/{id}/send quotes:write Ipadala; email_customer=true para i-email ito kasama ang PDF
POST /quotes/{id}/accept quotes:write I-record ang pagtanggap; /decline na may reason para i-record ang pagtanggi
POST /quotes/{id}/convert quotes:write Gawing draft na invoice nang isang beses; kailangan din ng invoices:write
DELETE /quotes/{id} quotes:write Burahin ang draft na quotation
GET /expenses expenses:read Listahan ng gastos; ?from=, ?to=, ?category=, ?q=
GET /expenses/{id} expenses:read Isang gastos
POST /expenses expenses:write Mag-record ng gastos: spent_on, category, description, amount, tax_amount, method
PUT /expenses/{id} expenses:write I-edit ang gastos
DELETE /expenses/{id} expenses:write Burahin ang gastos
GET /projects projects:read Listahan ng proyekto; ?status=open|all|..., ?customer_id=, ?q=
GET /projects/{id} projects:read Isang proyekto kasama ang buod ng na-invoice, gastos at margin
POST /projects projects:write Gumawa ng proyekto: customer_id, name, status, contract_value
PUT /projects/{id} projects:write I-edit ang proyekto
DELETE /projects/{id} projects:write Burahin ang proyektong walang invoice o gastos
GET /products products:read Catalog ng produkto at serbisyo; ?type=service|goods, ?q=
GET /products/{id} products:read Isang entry sa catalog
POST /products products:write Idagdag sa catalog: type, name, sku, unit, unit_price
PUT /products/{id} products:write Mag-edit ng entry sa catalog; hindi nagbabago ang mga kasalukuyang invoice
DELETE /products/{id} products:write Alisin sa catalog
GET /deals deals:read Listahan ng deal; ?stage=open|won|..., ?customer_id=, ?owner_id=
GET /deals/{id} deals:read Isang deal
POST /deals deals:write Gumawa ng deal: customer_id, title, stage, value
PUT /deals/{id} deals:write I-edit ang deal
POST /deals/{id}/stage deals:write Palitan ang stage; lost_reason kapag talo
DELETE /deals/{id} deals:write Burahin ang deal
GET /activities activities:read Listahan ng aktibidad; ?customer_id=, ?deal_id=, ?follow_up=due
POST /customers/{id}/activities activities:write Mag-record ng aktibidad: type, happened_on, body, follow_up_on
POST /activities/{id}/complete activities:write Markahang tapos ang isang follow-up
DELETE /activities/{id} activities:write Burahin ang aktibidad
GET /reports/aging reports:read Edad ng receivables ayon sa customer; ?as_of=
GET /reports/revenue reports:read Na-invoice at natanggap bawat buwan; ?year=
GET /reports/cash-flow reports:read Pumasok at lumabas na pera bawat buwan; ?year=
GET /reports/profit-and-loss reports:read Simpleng kita at lugi; ?year=, ?month=
GET /reports/vat reports:read Output at input VAT bawat buwan; ?year=
GET /reports/quotes reports:read Mga quotation na naipadala at tinanggap bawat buwan, acceptance rate; ?year=
GET /reports/recurring-revenue reports:read Katumbas na recurring revenue bawat buwan at mga invoice sa susunod na 90 araw
GET /agents agents:read Mga agent na puwede mong patakbuhin, ang input nila, at kung handa na ang AI
POST /agents/{agent}/runs agents:run Magsimula ng agent; magpadala ng Idempotency-Key header para ligtas ang pag-ulit
GET /agent-runs agents:read Mga agent run; ?status=, ?agent=
GET /agent-runs/{id} agents:read Isang agent run kasama ang status history nito; nagli-link sa web app ang mga approval
POST /agent-runs/{id}/cancel agents:run Kanselahin ang agent run

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.createdNaidagdag ang customer
customer.updatedNa-update ang customer
customer.deletedNabura ang customer
invoice.createdNagawa ang draft na invoice
invoice.updatedNa-update ang draft na invoice
invoice.sentInilabas ang invoice
invoice.paidBayad na ang invoice
invoice.voidedKinansela ang invoice
invoice.deletedNabura ang draft na invoice
invoice.payment_recordedNaitala ang bayad sa invoice
invoice.payment_deletedNabura ang record ng bayad
quote.createdNagawa ang draft na quotation
quote.updatedNa-edit ang draft na quotation
quote.sentNaipadala ang quotation
quote.acceptedTinanggap ng customer ang quotation
quote.declinedTinanggihan ng customer ang quotation
quote.deletedNabura ang draft na quotation
deal.createdNagawa ang deal
deal.updatedNa-edit ang deal
deal.stage_changedNagbago ang stage ng deal (kasama ang panalo o talo)
deal.deletedNabura ang deal
project.createdNagawa ang proyekto
project.updatedNa-edit ang proyekto
project.deletedNabura ang proyekto

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.