---
title: "Record an expense"
method: POST
path: "/api/v1/rest/spese/expenses"
tags: ["Expenses"]
---

# Record an expense

`POST /api/v1/rest/spese/expenses`

## Request

- Base URL: `https://api.custodo.ai`
- URL: `https://api.custodo.ai/api/v1/rest/spese/expenses`
- Auth: HTTP bearer

Files a line against the caller’s own name — whoever the credential belongs to is who gets reimbursed, and this tool cannot file on somebody else’s behalf. `amount_cents` is integer cents (42,00 € is 4200) and may be omitted when nobody has read the receipt yet: an incomplete line is accepted on purpose and only refused later, at the moment somebody tries to put it in a nota spese. ⚠ A CAR JOURNEY (`kind: "km"`) IS PRICED BY THE SERVER: give `distance_km` and never an amount or a currency — the company’s kilometric rate, or the traveller’s own, is applied and frozen onto the line. No receipt image can be attached through this tool; photographing the giustificativo is something the person does in the app, and the line waits for it.

## Headers

- `X-Tenant-Id` string, uuid

## Request body

- object
  - `kind` 'receipt' | 'km' | 'document', required: `receipt` a purchase, `km` a car journey, `document` an invoice or booking.
  - `spent_on` string, required: The day the money was spent, YYYY-MM-DD.
  - `supplier` string: Who was paid, as written on the receipt. Not matched against any list.
  - `amount_cents` integer: Integer cents in `currency`. Omit when it is not known yet. Never on a `km` line.
  - `currency` string: ISO-4217, three letters. Omit for the company’s own currency, which is the normal case. A foreign-currency line is converted later at the published rate.
  - `category_id` string, uuid: From spese_list_categories. A TOP-LEVEL one.
  - `subcategory_id` string, uuid: A child of `category_id`, which must be given alongside it.
  - `payment_channel_id` string, uuid: From spese_list_channels. It decides whether the company owes the money back, and the answer is frozen onto the line as it is written.
  - `note` string: Anything worth recording — who was at the table, what the trip was for.
  - `distance_km` number: Kilometres driven. Required on a `km` line and refused on every other kind.
  - `metadata` object: The extra fields the chosen category (and sub-category) declares, keyed by their `name` from spese_list_categories, each of the type it declares. A key the category does not declare, a value of the wrong type or one outside a field’s list is refused with the reason and nothing is written; a line with no category takes no fields.

## Response `200`

Done. `data` is the tool’s own answer.

- RestResult
  - `ok` true, required
  - `data` unknown, required

## Other responses

- `400`: The arguments did not validate: `error.message` names the field, what it expects and what arrived.
- `401`: No API key, or one that is malformed, unknown, revoked or expired.
- `403`: The call never ran: the module is off for this organisation, the credential lacks the permission or the module, the tier does not cover it, or the tool is destructive and the key was not minted for that.
- `409`: The record is in a state that does not allow it — under legal hold, already sent, already closed. `error.code` says which.
- `422`: The tool ran and refused, in a sentence of its own (`TOOL_REFUSED`): the message says what to change. Retrying the same call will not help.
- `429`: The API key has spent its minute, or its day. Every request a key makes counts. See `Retry-After`.

---

[API](https://skmtc.dev/custodo/apis/custodo-integrazioni.md) · [All operations](https://skmtc.dev/custodo/apis/custodo-integrazioni/llms.txt) · [OpenAPI document](https://skmtc.dev/custodo/apis/custodo-integrazioni/revisions/16d336d3ec2a?raw)
