Bills
Purchase documents with their lines, written and read back. A bill explains the payables figure the trial balance carries; it never moves it.
Writing bills
The endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/organisations/{organisation}/bills |
Writes the documents and their lines |
| POST | /api/v1/organisations/{organisation}/bills/validate |
The same checks; nothing is written |
Needs a bearer token, a credential issued for that company, and the credential's bills.write
scope.
The body
{
"declarations": {
"source_reference": "netsuite-ap",
"period": "2026-09",
"value_basis": "company_base",
"rate_direction": "document_to_base",
"base_currency": "SGD",
"mode": "replace_period"
},
"records": [
{
"external_id": "BILL-7712",
"number": "SUP-2026-0431",
"party_name": "Pacific Paper Supplies",
"party_city": "Kuala Lumpur",
"party_country": "MY",
"date": "2026-09-02",
"due_date": "2026-10-02",
"total": 2400.00,
"amount_due": 2400.00,
"lines": [
{
"line_item_id": "BILL-7712-1",
"account_code": "5000",
"description": "Carton stock",
"amount": 2400.00
}
]
}
]
}
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 bill. It is the record's identity. |
number | string | no | The supplier's number, as a reader sees it. |
party_name | string | yes | The supplier's name, stored as sent. |
party_city, party_country | string | no | Where the supplier 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. |
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. |
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.
What it does
- Two ways to send.
replace_periodsends the whole month and restates it, so a bill absent from the payload is removed.mergeadds and updates only. - An unknown account code refuses the record. Nothing is written for that bill, the rest of the payload is still written, and the accounts kind is named in the answer.
- The total must agree with the lines within the stated tolerance.
- Status follows the money, not the word the source sends.
What it answers
{
"id": 92, "kind": "bills", "source_reference": "netsuite-ap",
"period": "2026-09", "status": "written", "records_received": 21,
"counts": { "created": 21, "updated": 0, "unchanged": 0, "rejected": 0 },
"recalculation": null,
"written_at": "2026-10-23T06:33:52+00:00",
"links": { "self": "…/requests/92", "results": "…/requests/92/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 | A bill 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 |
Invoices is the same shape against the sales side.
Reading bills
The purchase documents the company holds, with their lines: the same shape as invoices against the other side.
The endpoint
| Method | Path | Answers |
|---|---|---|
| GET | /api/v1/organisations/{organisation}/bills |
The bills and their lines |
Needs a bearer token, a credential issued for that company, and the bills.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-BILL-177-2026-09-2", "number": "BILL-177-2026-09-2",
"party": { "name": "Adventure Works", "city": "Portland", "country": "United States" },
"date": "2026-09-01", "due_date": "2026-10-01", "payment_date": null,
"status": "PAID", "currency": "SGD", "exchange_rate": 1,
"sub_total": 8589, "total_tax": 0, "total_discount": 0, "total": 8589,
"amount_due": 0, "amount_paid": 8589, "provider": "Import",
"lines": [
{ "line_item_id": "DEMO-BILL-177-2026-09-2-1", "account_code": "SA-5000",
"description": "Add-on Modules", "quantity": 1, "unit_price": 6012.3,
"amount": 6012.3, "tax_amount": 0, "tax_type": null, "tax_inclusive": false }
]
}
],
"page": 1, "per_page": 50, "total": 2, "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. |