Expenses

Recorded costs and purchase invoices. Each expense is returned with its embedded vendor summary and chart-of-accounts reference.

Operations

Amount convention: amount is the cost excluding BTW (net), and vat_amount is the BTW itself — amount + vat_amount is what the receipt's total says. This matches what the ledger and the BTW-aangifte read. The web form asks users for the receipt total and derives the net; API clients are expected to send the net directly.

Credit notes carry a negative sign in responses. Send positive magnitudes on create and update, whatever the type — the server stores and returns a credit_note expense with negative amount and vat_amount, so summing a list of expenses nets credit notes off without special-casing. Changing type re-signs the stored values accordingly.

VAT treatment: the optional purchase_tax_treatment (default domestic) decides what vat_amount may hold, because vat_amount is always BTW you can deduct. For reverse_charged_domestic, reverse_charged_eu and reverse_charged_non_eu, the invoice carries no BTW, so a vat_amount you send is dropped; billey calculates the BTW you self-account from amount and vat_rate. For foreign_vat_charged (a supplier outside the Netherlands charged BTW that you cannot deduct), send the net and the BTW on the invoice as usual. billey adds that BTW to the cost, so the response returns the total as amount, null as vat_amount, and the BTW on the invoice as non_deductible_vat_amount. A later PATCH of amount is still the net: send amount minus non_deductible_vat_amount, not the total you read back. Every response also carries purchase_tax_treatment.

Method & path Ability
GET /api/v1/expenses expenses:read
GET /api/v1/expenses/{expense} expenses:read
POST /api/v1/expenses expenses:write
PATCH /api/v1/expenses/{expense} expenses:write
DELETE /api/v1/expenses/{expense} expenses:write

Create an expense

A create always makes a Draft — any status you send is ignored (change it later with a PATCH). Required: type (invoice, receipt, credit_note, or subscription), vat_rate (0, 9, or 21), and date. As a create it takes an Idempotency-Key.

curl -X POST https://billey.nl/api/v1/expenses \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_id": 3,
    "type": "receipt",
    "amount": "49.99",
    "vat_rate": 21,
    "date": "2026-07-21",
    "description": "Printer paper"
  }'
{
  "data": {
    "id": 88,
    "description": "Printer paper",
    "type": "receipt",
    "status": "draft",
    "amount": "49.99",
    "vat_rate": "21.00",
    "date": "2026-07-21",
    "vendor": { "id": 3, "name": "Office Supplies B.V." },
    "account": { "id": 12, "code": "4400", "name": "Office costs" }
  }
}

Attach a source document

On create you may attach a source document in one of two mutually exclusive ways (sending both is a 422):

  • Link an existing one — pass document_id (JSON or multipart). Useful for a document that already arrived by email ingestion. It must exist in this administration and not already be linked to another expense.
  • Upload a new one — send the request as multipart/form-data with a file part alongside the expense fields. The file is stored as a first-class document (source upload) and linked to the new expense, so a scanned receipt (tankbon) or a vendor-invoice PDF goes in with a single call:
curl https://app.billey.nl/api/v1/expenses \
  -H "Authorization: Bearer <token>" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "type=receipt" \
  -F "vat_rate=21" \
  -F "date=2026-07-21" \
  -F "amount=49.99" \
  -F "file=@/path/to/tankbon.jpg"

The file must be ≤ 10 MB and one of pdf, jpg, jpeg, png, webp, heic, tiff, tif, bmp, xml (validated by content, not the filename). Its searchable text is extracted asynchronously, so the returned document starts at extraction_status: processing; the stored bytes are available immediately via GET /api/v1/documents/{document}/download. Attaching a document (either way) is accepted only on create.

Update an expense

PATCH is a genuine partial merge — only the keys you send change (contrast the invoice PATCH; see PATCH behaviour). Status transitions (Draft → Pending → Paid) ride the status field here, which is accepted only on update, never on create.

Delete an expense

DELETE soft-deletes the expense (and cleans up an orphaned source document), returning 204. Refused with 409 for a period-locked or asset-linked expense.

See the OpenAPI spec for the full field list.

Klopt er iets niet of ontbreekt er iets? support@billey.nl