Invoices
Post a sales invoice through the same producer the app uses — numbering, approval, journals and settlement order all apply.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /invoices | List — page, page_size, search, from, to, derived_status |
| POST | /invoices | Create — receiver, date and at least one line |
| GET | /invoices/{id} | Detail |
| GET | /invoices/{id}/pdf | The customer-facing PDF (application/pdf) |
Scope sales_invoices. The document type is pinned by the route — you never name it.
Creating one
curl -X POST https://api.trabalance.com/api/v1/invoices \
-H "Authorization: Bearer tk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2f6b3e90-1c44-4c1a-9d2b-1e0c7a1f9a55" \
-d '{
"receiver": "<customer id>",
"date": "2026-08-26",
"dueDate": "2026-09-25",
"subject": "August services",
"items": [
{ "id": "<item id>", "qty": 2, "rate": 150.00, "description": "Consulting day" }
]
}'| Field | Required | Notes |
|---|---|---|
| receiver | Yes | Customer id from `/customers` |
| date | Yes | YYYY-MM-DD. This is the business date settlement is ordered by — not the moment you called. |
| items[] | Yes, at least one | Each line needs `id` (an item), `qty` above 0 and `rate` of 0 or more. Optional per line: description, discount, discount_type, tax. |
| dueDate, subject, currency, exchange_rate | No | currency is ISO 4217 and defaults to the business currency |
| center | No | A location id — and it must be inside the bound user's location allow-list |
| documentDiscount | No | A discount on the whole document |
| status | No | `2` posts it (the default: numbered and journalled). `1` saves a draft. |
What happens that you did not ask for
This is the point of going through the producer. One POST also:
- allocates the next invoice number in the business's own sequence and returns it as
no; - runs the business's approval thresholds — an invoice over a configured limit lands awaiting approval rather than posted;
- posts the double-entry journal, atomically with the document. If the journal cannot post, the invoice does not exist;
- queues the business's auto-matching, where that is switched on, so unapplied customer credit is settled against the invoice in the background — statement balance first, then documents by their own date. The
POSTreturns before that runs, so read the paid state back rather than assuming it; - emits
invoice.createdto every webhook subscriber, in the same transaction as the fact.
Send quantities and rates. Do not send a total — the backend computes every figure, including tax and discount, and the response carries the authoritative numbers. A total you calculated is not consulted.
Listing
curl "https://api.trabalance.com/api/v1/invoices?from=2026-08-01&to=2026-08-31&derived_status=overdue&page_size=50" \
-H "Authorization: Bearer tk_live_…"derived_status filters on the paid state the backend derives (for example unpaid, paid, overdue) rather than a raw status column, so it means the same thing here as it does on the invoice list in the app.
The PDF
GET /invoices/{id}/pdf returns application/pdf — the same render path the app and outbound email use, so the business's logo (or the location's own logo, where one is set) is on it.
Related
- Customers and vendors
- Receipts & payments — mark it paid
- Idempotency
- Available events