FinzBooksDevelopers

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"
}
GET/invoicesList invoicesAIBooks.invoices.READ

Query params

ParamNotes
per_page1 – 200. Default 50.
cursorOpaque next_cursor.
statusOne InvoiceStatus value.
customer_idFilter to one customer.
date_from / date_toISO date bounds.
GET/invoices/{invoice_id}Get one invoiceAIBooks.invoices.READ
POST/invoicesCreate an invoice (DRAFT by default)AIBooks.invoices.CREATE

Body

FieldTypeNotes
customer_idstringRequired.
datedatetimeRequired.
place_of_supplystringGST state code (e.g. IN-KA). Required.
line_items[]array≥1 item.
invoice_numberstring?Override the auto-generated number. Must be unique within the branch. When set, the series sequence is NOT consumed.
series_namestring?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_idstring?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_datedatetime?Defaults to date + payment_terms.
currency_codestringDefault INR.
exchange_ratefloatDefault 1.0.
notesstring?
auto_approveboolIf 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.

GET/invoices/seriesList invoice number series available on a branchAIBooks.invoices.READ

Query params

ParamNotes
branchIdResolve series for this branch. Falls back to the org's default branch when omitted.
transactionDateFY 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

FieldTypeMeaning
series[].seriesNamestringPass this verbatim as series_name in POST /invoices.
series[].labelstringHuman-readable label for picker UIs.
series[].prefixstringThe number prefix that will be generated (e.g. EXP-).
series[].isDefaultboolTrue for exactly one entry — the org's chosen default.
series[].periodIdstring?Financial-year period this series belongs to. null when no period is configured.
branchIdstring?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 sendResult
series_name onlyAuto-generates the next number from that series's sequence (e.g. EXP-2026-0042).
invoice_number onlyUses your number verbatim. The series sequence is not consumed. Number must be unique per branch or you get 400 validation.invalid_value.
BothYour invoice_number wins. series_name is ignored for the numbering decision.
NeitherAuto-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.

PUT/invoices/{invoice_id}Update header fields (DRAFT only)AIBooks.invoices.UPDATE

Only reference_number, due_date, and notes are editable, and only while status is DRAFT. To change line items, void and recreate.

POST/invoices/{invoice_id}/approveApprove (post journal, DRAFT → SENT)AIBooks.invoices.UPDATE
POST/invoices/{invoice_id}/voidVoid (any status → CANCELLED)AIBooks.invoices.UPDATE

Voiding posts the reversal journal automatically. Paid invoices can't be voided directly — reverse the payment first.

DELETE/invoices/{invoice_id}Hard delete (DRAFT only)AIBooks.invoices.DELETE