---
title: "Update an expense"
method: PUT
path: "/expenses/{id}"
tags: ["Expenses"]
---

# Update an expense

`PUT /expenses/{id}`

## Request

- Base URL: `https://api.lumabill.ch/v1`
- URL: `https://api.lumabill.ch/v1/expenses/{id}`
- Auth: HTTP bearer

Partial update. The stored exchange rate is kept unless `currency` changes or a new `exchangeRate` is sent (editing lines, amount or date does not re-rate the expense). `exchangeRate: null` (not omitted) drops the stored rate: the expense converts at the ESTV/BAZG rate of its date again; refused with CURRENCY_LOCKED when the expense is paid or revalued. The app's revenue update (PUT /revenue/{id}, not part of this spec) follows the same rule. Currency errors (400): UNSUPPORTED_CURRENCY (with `currency`), CURRENCY_LOCKED (currency or rate of a booked or paid expense), FX_REVALUATION_LIVE (amount or date of a foreign-currency expense in a fiscal year with a booked FX revaluation), INVALID_EXCHANGE_RATE, EXCHANGE_RATE_IMPLAUSIBLE, AMOUNT_OUT_OF_RANGE (base amount beyond 9'999'999'999.99), VAT_CODE_NOT_VALID_FOR_DATE (e.g. 7.7 % dated 2024 or later).

## Request body

- ExpenseWrite
  - `vendor_id` string, uuid, nullable
  - `description` string, required
  - `amount` number, double, required
  - `currency` 'CHF' | 'EUR' | 'USD' | 'GBP' | 'JPY' | 'CAD' | 'AUD' | 'SEK' | 'NOK' | 'DKK' | 'HUF', required: ISO 4217 code of a currency the API converts (ESTV/BAZG rates). The same list applies to every currency field of the API.
  - `exchangeRate` number, double, nullable: Foreign-currency expense only: CHF per 1 unit of `currency`, a positive number (400 INVALID_EXCHANGE_RATE otherwise; 400 EXCHANGE_RATE_IMPLAUSIBLE when outside 0.5x to 2x of the ESTV/BAZG rate of the date (0.0001 to 100 without one; the body carries `officialRate`, `min`, `max`)). Omit to use the ESTV/BAZG rate of the expense date (the monthly average rate when the profile uses the Monatsmittelkurs for that year). On an update the stored rate is kept unless `currency` changes or a new `exchangeRate` is sent; an omitted `exchangeRate` keeps it, an explicit `null` drops a stored manual rate and converts at the ESTV/BAZG rate of the expense date again. A booked, paid or revalued expense keeps its currency and rate (400 CURRENCY_LOCKED, also for `null`).
  - `date` string, date, required
  - `category` string, nullable
  - `vat_rate` number, double, nullable
  - `receipt_url` string, uri, nullable
  - `lines` object[], nullable: Full replacement line items (update only). Required when changing amount or vat_rate so the recognition journal entry can be rebuilt; the expense total becomes the sum of line amounts.
    - `description` string
    - `amount` number, double, required
    - `taxRate` number, double, nullable
    - `debitAccountId` string, uuid, nullable: Chart-of-accounts UUID to debit (see /chart-accounts). Required in double-entry mode unless the vendor's default account or `category` resolves one (400 EXPENSE_LINE_ACCOUNT_REQUIRED).
    - `vatCode` string, nullable

## Response `200`

Updated expense

- Expense
  - `id` string, uuid, required
  - `profile_id` string, uuid, required
  - `vendor_id` string, uuid, nullable
  - `description` string, required
  - `amount` number, double, required
  - `currency` 'CHF' | 'EUR' | 'USD' | 'GBP' | 'JPY' | 'CAD' | 'AUD' | 'SEK' | 'NOK' | 'DKK' | 'HUF', required: ISO 4217 code of a currency the API converts (ESTV/BAZG rates). The same list applies to every currency field of the API.
  - `exchangeRate` number, double, nullable: CHF per 1 unit of `currency` the expense was booked at; null in the base currency
  - `baseCurrencyAmount` number, double, nullable: The expense total in CHF
  - `date` string, date, required
  - `category` string, nullable
  - `vat_rate` number, double, nullable
  - `receipt_url` string, uri, nullable
  - `created_at` string, date-time, required

## Other responses

- `400`: Validation error
- `401`: Invalid or missing API key
- `403`: Subscription required or read-only key on a write endpoint
- `404`: Resource not found
- `429`: Rate limit exceeded
- `500`: Internal server error

---

[API](https://skmtc.dev/lumabill/apis/lumabill-api.md) · [All operations](https://skmtc.dev/lumabill/apis/lumabill-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lumabill/apis/lumabill-api/revisions/e48e7099b545?raw)
