The Invoices endpoint has full parity with the internal Billbooks app, not a stubbed subset. That means creating or editing an invoice through the API triggers the same real side effects a staff member creating one by hand would: stock gets deducted, expenses get linked, client balances move. This guide walks through all of it so nothing surprises you in production.
Create an invoice
A few fields worth understanding before you script this:
no is your invoice number. It must be unique per organization. Reuse one and you get a 409 Conflict.
- Each line needs either an
item_id (pulled from your catalog) or a plain description, at least one of the two. qty must be greater than 0, rate must be 0 or more, and duplicate item_id values in the same request are rejected with 422.
subtotal, discount, taxtotal, and nettotal are always computed server-side from items. If you send those fields in the request body, they’re ignored. Only discount_type, discount_value, and shipping actually influence the computed totals.
status defaults to "draft" if you omit it. Set it to "sent" to send the invoice immediately.
What happens behind the scenes
Creating an invoice isn’t just a database insert. For each line item:
- If the line resolves to an
item_id whose catalog item has track_inventory turned on, stock is deducted and a ledger row is written recording the sale. See Build your item catalog for how track_inventory works.
- If the line carries an
expense_id, that expense is marked as invoiced and linked to this invoice. See Log expenses via API.
- If
status resolves to anything other than "draft", the client’s running balance is incremented by the invoice’s nettotal.
None of this is optional or togglable. If you’re building a script that creates draft invoices for review before sending, keep in mind the stock and balance effects only fire once you move status off "draft".
Editing an invoice
items is always a full replace on update, never a patch. Every line you want on the invoice after the edit has to be in the array, including ones that were already there. Any existing line you omit gets deleted, and its stock and expense-linking side effects are reversed as part of that deletion.
Every field other than items follows normal partial-patch rules: only what you submit changes. Changing status or client_id correctly reconciles the client balance delta, which is actually more complete than the internal app’s own edit screen (that one can’t change status or client at all).
One restriction: through this endpoint, status can only be set to draft or sent. Moving an invoice to paid, partial, overdue, void, or recurring is rejected with 422, those states are only reachable from inside the main app (typically as the result of a recorded payment).
Deleting an invoice
Deletion is blocked with 409 Conflict if the invoice has any recorded payment, which is actually a stricter guard than the internal app’s own delete function has. If the delete succeeds, all stock and expense-linking side effects from that invoice are reversed line by line, and if the invoice wasn’t a draft, the client’s balance is decremented by the invoice’s balance.
What’s next