Skip to main content
Expenses can stand alone (just a record of money spent) or get linked to an invoice so a client is billed for them. That link is one-directional and system-managed, and there’s a real gap in how deletion is guarded that’s worth reading before you build against this endpoint.

Create an expense

Don’t send is_invoiced or invoice_id on create, even set to false or 0. Submitting either field at all rejects the entire request with 422. Those two fields are system-managed and only ever get set by the Invoices endpoint’s own linking logic, when a line item on an invoice bills this expense.

How invoice-linking actually happens

There’s no “link this expense to that invoice” call on the Expenses endpoint. The link is created from the other direction: when you create or edit an invoice and include a line item with an expense_id, that’s what sets the expense’s is_invoiced and invoice_id fields. See Create and send an invoice for the invoice side of this. If you edit that invoice later and remove the line referencing the expense (remember, items is a full replace on invoice PUT), the link is reversed as part of that edit.

List and filter expenses

Available filters: vendor, category_id, client_id, project_id, datestart/dateend (date range), currency, and free-text q. filter[status] accepts pass-through pseudo-filters like unbilled, invoiced, billable, non-billable, withreceipts, withoutreceipts, and recurring, not independently re-validated by this endpoint, they’re whatever the underlying expense model accepts.

Deleting an expense

This is a real gap, not a documented design choice: deleting an expense has no guard rail at all, unlike Clients or Items, which block deletion when the record is already in use. An expense that’s already linked to an invoice (is_invoiced is true) can be deleted outright through this endpoint. Doing so orphans the invoice line item that references it, the invoice keeps the line, but the expense record behind it is gone.
If your integration deletes expenses programmatically, check is_invoiced yourself first and treat a true value as a reason to hold off, the API won’t stop you.

What’s next