Invoices
Customer invoices with line items, GST split, and journal posting. Item-driven line auto-fill: send { item_id, quantity } and the server hydrates name / rate / tax / HSN from the item master.
Resource shape
{
"invoice_id": "inv_abc",
"invoice_number": "INV-2026-0042",
"customer_id": "ct_acme",
"customer_name": "Acme Corp",
"date": "2026-05-12T00:00:00",
"due_date": "2026-06-11T00:00:00",
"reference_number": "PO-991",
"place_of_supply": "IN-MH",
"currency_code": "INR",
"exchange_rate": 1.0,
"line_items": [
{
"line_item_id": "li_1",
"item_id": "it_widget",
"name": "Widget",
"description": "Widget",
"hsn_or_sac": "8421",
"quantity": 2,
"unit": "NOS",
"rate": 100,
"discount": 0,
"tax_id": "tx_gst_18",
"tax_percentage": 18,
"item_total": 200
}
],
"sub_total": 200,
"tax_total": 36,
"total": 236,
"payment_made": 0,
"balance": 236,
"status": "DRAFT", // DRAFT | SENT | PARTIALLY_PAID | PAID
// | OVERDUE | CANCELLED | CREDIT_APPLIED
"notes": null,
"created_time": "2026-05-12T08:30:00",
"last_modified_time": "2026-05-12T08:30:00"
}/invoicesList invoicesAIBooks.invoices.READQuery params
| Param | Notes |
|---|---|
per_page | 1 – 200. Default 50. |
cursor | Opaque next_cursor. |
status | One InvoiceStatus value. |
customer_id | Filter to one customer. |
date_from / date_to | ISO date bounds. |
/invoices/{invoice_id}Get one invoiceAIBooks.invoices.READ/invoicesCreate an invoice (DRAFT by default)AIBooks.invoices.CREATEBody
| Field | Type | Notes |
|---|---|---|
customer_id | string | Required. |
date | datetime | Required. |
place_of_supply | string | GST state code (e.g. IN-KA). Required. |
line_items[] | array | ≥1 item. |
invoice_number | string? | Override the auto-generated number. Must be unique within the branch. When set, the series sequence is NOT consumed. |
series_name | string? | Which number series to consume — e.g. default, export, rcm. Branch-scoped. Falls back to default when omitted. List available via GET /invoices/series. |
branch_id | string? | Branch under which to issue the invoice. Determines which series sequence is consumed AND whether tax routes intra-state (CGST+SGST) vs inter-state (IGST). Defaults to the org's default branch. |
due_date | datetime? | Defaults to date + payment_terms. |
currency_code | string | Default INR. |
exchange_rate | float | Default 1.0. |
notes | string? | |
auto_approve | bool | If true, posts journal immediately and sets status to SENT. |
Line item shape
Minimum input: either item_id (auto-fills the rest from the item master) or name + rate (ad-hoc line).
{
"item_id": "it_widget", // inherits name / rate / tax_id / hsn from master
"quantity": 2,
"rate": null, // overrides item.selling_price if set
"tax_id": null, // overrides item.tax_id if set
"discount": 0,
"name": null,
"hsn_or_sac": null,
"unit": null
}Example
POST /invoices
Authorization: Bearer aibk_pat_…
Content-Type: application/json
Idempotency-Key: …
{
"customer_id": "ct_acme",
"date": "2026-05-12T00:00:00",
"place_of_supply": "IN-MH",
"line_items": [{ "item_id": "it_widget", "quantity": 2 }],
"auto_approve": true
}Multi-series numbering (CGST Rule 46)
Indian GST allows an organisation to maintain multiple invoice number series per branch — typically one for domestic (default), one for exports (export), one for reverse-charge (rcm), etc. Each series has its own sequence; numbers must be unique within a branch.
1. List available series
Always call this first so your integration knows which series_name values are valid for the active branch. The response always contains at least one entry — a virtual default if no DocumentSequence rows are configured yet.
/invoices/seriesList invoice number series available on a branchAIBooks.invoices.READQuery params
| Param | Notes |
|---|---|
branchId | Resolve series for this branch. Falls back to the org's default branch when omitted. |
transactionDate | FY context for series resolution. Defaults to today (ISO date YYYY-MM-DD). |
Request
curl -X GET 'http://localhost:8000/api/public/v1/invoices/series?branchId=branch_abc' \
-H 'Authorization: Bearer aibk_pat_…'Response
{
"series": [
{
"seriesName": "default", // value to send as series_name when creating
"label": "Default", // display label for your picker UI
"prefix": "INV-", // prefix the numbers will carry
"isDefault": true, // marks the org's chosen default series
"periodId": "fp_2026_27" // the FY period this series belongs to
},
{
"seriesName": "export",
"label": "Export",
"prefix": "EXP-",
"isDefault": false,
"periodId": "fp_2026_27"
},
{
"seriesName": "rcm",
"label": "RCM",
"prefix": "RCM-",
"isDefault": false,
"periodId": "fp_2026_27"
}
],
"branchId": "branch_abc" // the branch the response was resolved for
}Field reference
| Field | Type | Meaning |
|---|---|---|
series[].seriesName | string | Pass this verbatim as series_name in POST /invoices. |
series[].label | string | Human-readable label for picker UIs. |
series[].prefix | string | The number prefix that will be generated (e.g. EXP-). |
series[].isDefault | bool | True for exactly one entry — the org's chosen default. |
series[].periodId | string? | Financial-year period this series belongs to. null when no period is configured. |
branchId | string? | Echoes back which branch the response was resolved for. null means the org-level default branch. |
2. Numbering precedence
The public API decides which number to apply on POST /invoices in this order:
| You send | Result |
|---|---|
series_name only | Auto-generates the next number from that series's sequence (e.g. EXP-2026-0042). |
invoice_number only | Uses your number verbatim. The series sequence is not consumed. Number must be unique per branch or you get 400 validation.invalid_value. |
| Both | Your invoice_number wins. series_name is ignored for the numbering decision. |
| Neither | Auto-generates from the default series of the resolved branch. |
Unknown series names don't error — the service falls back to legacy date-based numbering. That's why step 1 matters: always discover before you pick.
3. Create with a chosen series
POST /invoices
Authorization: Bearer aibk_pat_…
Content-Type: application/json
Idempotency-Key: …
{
"customer_id": "ct_acme",
"date": "2026-05-16T00:00:00",
"place_of_supply": "IN-MH",
"series_name": "export", // value from /invoices/series response
"branch_id": "branch_abc",
"line_items": [{ "item_id": "it_widget", "quantity": 2 }],
"auto_approve": true
}The response's invoice_number will be the next number from the export sequence, e.g. EXP-2026-0042.
Override — your number wins
POST /invoices
{
"customer_id": "ct_acme",
"date": "2026-05-16T00:00:00",
"place_of_supply": "IN-MH",
"invoice_number": "MY-CUSTOM-2026-001", // used as-is, series ignored
"branch_id": "branch_abc",
"line_items": [{ "item_id": "it_widget", "quantity": 2 }]
}Note: a custom invoice_number does not advance the series sequence. The next auto-generated invoice on the same series will continue from where the sequence was.
/invoices/{invoice_id}Update header fields (DRAFT only)AIBooks.invoices.UPDATEOnly reference_number, due_date, and notes are editable, and only while status is DRAFT. To change line items, void and recreate.
/invoices/{invoice_id}/approveApprove (post journal, DRAFT → SENT)AIBooks.invoices.UPDATE/invoices/{invoice_id}/voidVoid (any status → CANCELLED)AIBooks.invoices.UPDATEVoiding posts the reversal journal automatically. Paid invoices can't be voided directly — reverse the payment first.
/invoices/{invoice_id}Hard delete (DRAFT only)AIBooks.invoices.DELETE