Expenses

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

Operations

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.

Something inaccurate or missing? support@billey.nl