Invoices
Sales documents with their lines, written and read back. An invoice explains the receivables figure the trial balance carries; it never moves it.
Writing invoices
The endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/organisations/{organisation}/invoices |
Writes the documents and their lines |
| POST | /api/v1/organisations/{organisation}/invoices/validate |
The same checks; nothing is written |
Needs a bearer token, a credential issued for that company, and the credential's invoices.write
scope.
The body
{
"declarations": {
"source_reference": "netsuite-ar",
"period": "2026-09",
"value_basis": "document",
"rate_direction": "document_to_base",
"base_currency": "SGD",
"mode": "replace_period"
},
"records": [
{
"external_id": "INV-10241",
"number": "INV-2026-0091",
"party_name": "Harbour Logistics Pte Ltd",
"party_city": "Singapore",
"party_country": "SG",
"date": "2026-09-14",
"due_date": "2026-10-14",
"payment_date": null,
"currency_code": "USD",
"exchange_rate": 1.3521,
"sub_total": 1000.00,
"total_tax": 90.00,
"total": 1090.00,
"amount_due": 1090.00,
"lines": [
{
"line_item_id": "INV-10241-1",
"account_code": "4000",
"description": "Freight forwarding, September",
"quantity": 1,
"unit_price": 1000.00,
"amount": 1000.00,
"tax_amount": 90.00,
"tax_type": "GST",
"tax_inclusive": false
}
]
}
]
}
Declarations
| Declaration | Required | Value |
|---|---|---|
source_reference | yes | Your own id for the feed, 1–64 characters. |
period | yes | The month, YYYY-MM. |
value_basis | yes | document when the amounts are in each
document's currency, company_base when already converted. |
rate_direction | yes | Which way exchange_rate converts. |
base_currency | yes | The company's reporting currency. |
scenario | no | Actual (default), Budget or Forecast. |
mode | no | replace_period (default) for the whole month,
merge to add and update only. |
webhook_url | no | Called once when the request finishes or fails. |
The fields
| Field | Type | Required | What it means |
|---|---|---|---|
external_id | string | yes | The identity the source states for this invoice. It is the record's identity. |
number | string | no | The number a reader sees. |
party_name | string | yes | The customer's name, stored as sent. |
party_city, party_country | string | no | Where the customer is. |
date | date | yes | The document date. |
due_date | date | no | The due date. With the amounts, it decides the stored status. |
payment_date | date | no | When it was settled, if it was. |
status | string | no | The source's own word. The stored status is decided from the amounts and the due date, not from this field. |
total | number | yes | The document total. |
sub_total, total_tax, total_discount | number | no | Net, tax and discount totals. |
amount_due, amount_paid | number | no | The balance owed and what has been paid. |
currency_code | string | no | The document currency; the base currency when absent. |
exchange_rate | number | no | Required when the document currency is not the base currency, in the direction declared. A base-currency document states 1, or nothing. |
lines | array | yes | At least one line — see below. |
Each line carries line_item_id, account_code, description,
quantity, unit_price, amount, tax_amount,
tax_type and tax_inclusive. A line without an account code is refused with
line_requires_account.
Text is stored exactly as sent: no lookup, no shortening, no substitute value. A line sent without a description keeps an empty description.
What it does
- Two ways to send.
replace_periodsends the whole month and restates it, so an invoice absent from the payload is removed — the same outcome a connector's month re-read produces.mergeadds and updates only and removes nothing. - An unknown account code refuses the record. Nothing is written for that invoice, and the rest of the payload is still written. The accounts kind is named in the answer.
- The total must agree with the lines. A difference larger than the stated tolerance is
refused with
amount_disagrees_with_total. - Status follows the money. Whether an invoice reads as open, overdue or paid comes from its amounts and dates, not from the word the source sends.
What it answers
{
"id": 91, "kind": "invoices", "source_reference": "netsuite-ar",
"period": "2026-09", "status": "written", "records_received": 34,
"counts": { "created": 30, "updated": 3, "unchanged": 1, "rejected": 0 },
"recalculation": null,
"written_at": "2026-10-23T06:31:07+00:00",
"links": { "self": "…/requests/91", "results": "…/requests/91/results" }
}
Refusals
| Code | When |
|---|---|
account_not_found | A line names a code the company does not hold |
line_requires_account | A line carries no account code |
lines_are_required | An invoice carries no lines |
amount_disagrees_with_total | The total differs from the lines beyond the tolerance |
rate_required_for_foreign_currency | Another currency and no rate |
rate_must_be_one_for_base_currency | A base-currency document states a rate other than 1 |
duplicate_record_identity | Two records state the same
external_id |
Bills is the same shape against the purchase side.
Reading invoices
The sales documents the company holds, with their lines: what explains the receivables figure the trial balance carries.
The endpoint
| Method | Path | Answers |
|---|---|---|
| GET | /api/v1/organisations/{organisation}/invoices |
The invoices and their lines |
Needs a bearer token, a credential issued for that company, and the invoices.read scope.
The filters
| Parameter | What it does |
|---|---|
period | One month, Mon-YYYY, by the document date. |
external_id | One document. Unknown, it answers 404. |
account_code | Only documents with a line on that account. |
status | One stored status, such as OPEN or OVERDUE. A
non-posted status - DRAFT, VOIDED - is refused, because no document this read
answers can carry it. |
page, per_page | Paging, as everywhere. |
What it answers
{
"data": [
{
"external_id": "DEMO-IC-INV-177-2026-09-1", "number": "INV-177-2026-09-IC2",
"party": { "name": "Demo Service Company", "city": "Seattle", "country": "USD" },
"date": "2026-09-01", "due_date": "2026-10-01", "payment_date": null,
"status": "OVERDUE", "currency": "SGD", "exchange_rate": 1,
"sub_total": 19100, "total_tax": 0, "total_discount": 0, "total": 19100,
"amount_due": 19100, "amount_paid": 0, "provider": "Import",
"lines": [
{ "line_item_id": "DEMO-IC-INV-177-2026-09-1-1", "account_code": "SA-4110",
"description": "Support & Maintenance", "quantity": 1, "unit_price": 19100,
"amount": 19100, "tax_amount": 0, "tax_type": null, "tax_inclusive": false }
]
}
],
"page": 1, "per_page": 50, "total": 5, "has_more": false
}
| Field | What it says |
|---|---|
external_id | The identity the source stated for the document. |
number | The number a reader sees. |
party.name, party.city, party.country | The other party, stored as sent. |
date, due_date, payment_date | When it was raised, when it falls due, and when it was settled. |
status | The stored status, decided from the amounts and the due date - not the word the source sent. |
currency, exchange_rate | The document's own currency and the rate it was stored with. |
sub_total, total_tax, total_discount, total |
The document's totals. |
amount_due, amount_paid | What is still owed and what has been paid. |
provider | Who wrote it: API, Zoho, Xero,
QuickBooks, MYOB or an uploaded import. |
lines | One row per line: line_item_id, account_code,
description, quantity, unit_price, amount,
tax_amount, tax_type, tax_inclusive. |
Only posted documents are answered: a draft or a void one is not part of the figures, so it is not part of the read either.