Expenses
Expenses: a receipt's photo becomes an expense whose category, VAT, project and card fill in by themselves; the AI reviewer checks it against the company's expense policy, so a person sees only what it flags; it is booked as a bill and paid back in the next payment run. Company cards and their lines, matched to the receipts.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/entities/{entity}/expense-policy |
Get the expense policy | read |
|
PUT /v1/entities/{entity}/expense-policy |
Set the expense policy | admin |
|
GET /v1/expense-card-lines |
List card payments | read |
|
POST /v1/expense-card-lines |
Add card payments | admin |
required |
GET /v1/expense-cards |
List cards | read |
|
POST /v1/expense-cards |
Add a company card | admin |
required |
PATCH /v1/expense-cards/{card_id} |
Change a card | admin |
|
POST /v1/expense-cards/{card_id}/statement |
Read a card statement | admin |
required |
GET /v1/expenses |
List expenses | read |
|
POST /v1/expenses |
Claim an expense without a receipt | draft |
required |
GET /v1/expenses-reimbursement |
Where you are paid back | read |
|
PUT /v1/expenses-reimbursement |
Set where you are paid back | draft |
|
POST /v1/expenses/receipts |
Claim an expense from a receipt's photo | draft |
required |
GET /v1/expenses/{expense_id} |
Get an expense | read |
|
PATCH /v1/expenses/{expense_id} |
Change your draft | draft |
|
GET /v1/expenses/{expense_id}/receipt |
Download an expense's receipt | read |
|
POST /v1/expenses/{expense_id}/submit |
Submit your expense | draft |
|
POST /v1/expenses/{expense_id}/withdraw |
Withdraw your draft | draft |
GET /v1/entities/{entity}/expense-policy
Get the expense policy
What the AI reviewer holds an expense for: the limit per receipt and per expense
account, the oldest receipt taken and the amount above which a project is named. A company
that has not set its own has the defaults (default true).
| Required scope | read (admin implies every scope) |
| Operation id | get_expense_policy_v1_entities__entity__expense_policy_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Example request
curl "$ERP_URL/v1/entities/CH01/expense-policy" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | PolicyOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"category_limits": {
"6640": "80.00"
},
"max_age_days": 60,
"receipt_limit": "500.00"
}
PUT /v1/entities/{entity}/expense-policy
Set the expense policy
Set the company's expense policy (administrators). Audited with what it was.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | set_expense_policy_v1_entities__entity__expense_policy_put |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
Request body
application/json (required): PolicyUpdate
| Field | Type | Required | Description |
|---|---|---|---|
receipt_limit |
decimal string or null | A receipt above this, in the company's currency, waits for a person; null: no limit. (greater than 0) | |
category_limits |
map of decimal string | Lower limits per expense account code, e.g. meals {"6640": "80.00"}. |
|
max_age_days |
integer | A receipt older than this waits for a person. (min 1; max 3660; default 90) |
|
project_over |
decimal string or null | Above this amount an expense names a project or waits; null: never asked. (min 0) |
Example request
curl -X PUT "$ERP_URL/v1/entities/CH01/expense-policy" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"category_limits": {
"6640": "80.00"
},
"max_age_days": 60,
"receipt_limit": "500.00"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | PolicyOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"category_limits": {
"6640": "80.00"
},
"max_age_days": 60,
"receipt_limit": "500.00"
}
GET /v1/expense-card-lines
List card payments
The payments the card issuers listed, newest first, with the expense each was matched
to; without_receipt lists those still waiting for their receipt.
| Required scope | read (admin implies every scope) |
| Operation id | list_expense_card_lines_v1_expense_card_lines_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this company's (code or id). | |
| query | card |
string (uuid) or null | Only this card's. | |
| query | without_receipt |
boolean | Only those with no expense yet. (default false) |
|
| query | mine |
boolean | Only those of your own cards. (default false) |
|
| query | limit |
integer | How many, newest first. (min 1; max 5000; default 500) |
Example request
curl "$ERP_URL/v1/expense-card-lines" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of CardLineOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"card_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"last_four": "string",
"holder_name": "string",
"booked_on": "2026-09-21",
"amount": "string",
"currency": "string",
"merchant": "string",
"reference": "string",
"expense_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"expense_number": "string"
}
]
POST /v1/expense-card-lines
Add card payments
Payments as a card issuer lists them (a feed or an automation): each is matched by
itself to its holder's receipt (the same amount within five days); one read before is
skipped. An administrator's; needs an Idempotency-Key.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | add_expense_card_lines_v1_expense_card_lines_post |
Request body
application/json (required): CardLinesInput
| Field | Type | Required | Description |
|---|---|---|---|
lines |
array of CardLineInput |
yes | The payments, as the issuer lists them. (min items 1; max items 5000) |
Example request
curl -X POST "$ERP_URL/v1/expense-card-lines" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"amount": "84.50",
"booked_on": "2026-10-21",
"card": "3b0c8f4e-6a52-4f8e-9c1c-1f0d7a2b9e10",
"merchant": "Restaurant Krone"
}
]
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CardLinesResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"added": 1,
"already_there": 1,
"matched": 1,
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"card_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"last_four": "string",
"holder_name": "string",
"booked_on": "2026-09-21",
"amount": "string",
"currency": "string",
"merchant": "string",
"reference": "string",
"expense_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"expense_number": "string"
}
]
}
GET /v1/expense-cards
List cards
The company cards, by holder, with the vendor each is booked to and how many of its payments have no receipt yet.
| Required scope | read (admin implies every scope) |
| Operation id | list_expense_cards_v1_expense_cards_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this company's (code or id). |
Example request
curl "$ERP_URL/v1/expense-cards" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of CardOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"holder": "string",
"holder_name": "string",
"last_four": "string",
"currency": "string",
"vendor": "string",
"vendor_status": "string",
"active": true,
"open_lines": 1
}
]
POST /v1/expense-cards
Add a company card
A card a person pays with for the company (administrators): its holder and last four
digits. Its account with the issuer becomes a vendor of its own, activated through the
controls; what is bought with the card is booked to it and kept out of payment runs. Needs
an Idempotency-Key.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | add_expense_card_v1_expense_cards_post |
Request body
application/json (required): CardInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
holder |
string | yes | Who holds it: the person's e-mail address or id (user:<id>). |
last_four |
string | yes | The card number's last four digits. (pattern ^[0-9]{4}$) |
currency |
string or null | Its currency; default the company's. (pattern ^[A-Z]{3}$) |
Example request
curl -X POST "$ERP_URL/v1/expense-cards" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"holder": "anna@example.com",
"last_four": "4821"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CardOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"holder": "string",
"holder_name": "string",
"last_four": "string",
"currency": "string",
"vendor": "string",
"vendor_status": "string",
"active": true,
"open_lines": 1
}
PATCH /v1/expense-cards/{card_id}
Change a card
Stop using a card (active false) or use it again (administrators).
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | update_expense_card_v1_expense_cards__card_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | card_id |
string (uuid) | yes | Id of the card (GET /v1/expense-cards). |
Request body
application/json (required): CardUpdate
| Field | Type | Required | Description |
|---|---|---|---|
active |
boolean | yes | False: the card is no longer used. |
Example request
curl -X PATCH "$ERP_URL/v1/expense-cards/{card_id}" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"active": false
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CardOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"holder": "string",
"holder_name": "string",
"last_four": "string",
"currency": "string",
"vendor": "string",
"vendor_status": "string",
"active": true,
"open_lines": 1
}
POST /v1/expense-cards/{card_id}/statement
Read a card statement
Add the payments of a card's statement file and match each to its receipt, as POST /v1/expense-card-lines. Needs an Idempotency-Key.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | read_expense_card_statement_v1_expense_cards__card_id__statement_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | card_id |
string (uuid) | yes | Id of the card (GET /v1/expense-cards). |
Request body
multipart/form-data (required): Body_read_expense_card_statement_v1_expense_cards__card_id__statement_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The issuer's statement: CSV or .xlsx with a date, amount and merchant. |
Example request
curl -X POST "$ERP_URL/v1/expense-cards/{card_id}/statement" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@statement.xml"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CardLinesResult |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"added": 1,
"already_there": 1,
"matched": 1,
"lines": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"card_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"last_four": "string",
"holder_name": "string",
"booked_on": "2026-09-21",
"amount": "string",
"currency": "string",
"merchant": "string",
"reference": "string",
"expense_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"expense_number": "string"
}
]
}
GET /v1/expenses
List expenses
The expenses, newest first, with what each is (receipt, category, VAT, project, card), what the AI reviewer found, the bill it is booked as and whether it is paid back.
| Required scope | read (admin implies every scope) |
| Operation id | list_expenses_v1_expenses_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | Only this company's (code or id). | |
| query | mine |
boolean | Only the ones you paid. (default false) |
|
| query | status |
"approved" | "draft" | "held" | "posted" | "rejected" | "withdrawn" or null |
Only those in this status. | |
| query | limit |
integer | How many, newest first. (min 1; max 2000; default 200) |
Example request
curl "$ERP_URL/v1/expenses" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/expenses
Claim an expense without a receipt
A company card payment whose receipt is lost (card_line: its day, amount and merchant
are taken from it), or a ticket with no file: your draft, with the category and project
filled as for a receipt. Without a receipt the AI reviewer always holds it for a person.
Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_expense_v1_expenses_post |
Request body
application/json (required): ExpenseInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
card_line |
string (uuid) or null | The company card payment it is (GET /v1/expense-card-lines): its day, amount and merchant are taken from it. |
|
merchant |
string or null | Who was paid. (max length 200) | |
receipt_date |
string (date) or null | When; default today. | |
currency |
string or null | ISO 4217; default the company's. (pattern ^[A-Z]{3}$) |
|
total |
decimal string or null | What was paid, VAT included; needed without a card line. (greater than 0) | |
description |
string or null | What it was for, and why there is no receipt. (max length 500) |
Example request
curl -X POST "$ERP_URL/v1/expenses" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"card_line": "3b0c8f4e-6a52-4f8e-9c1c-1f0d7a2b9e10",
"description": "Lost"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
GET /v1/expenses-reimbursement
Where you are paid back
The vendor you are paid back as and its IBAN; all null before your first expense or account.
| Required scope | read (admin implies every scope) |
| Operation id | get_reimbursement_v1_expenses_reimbursement_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string or null | The company; default as above. |
Example request
curl "$ERP_URL/v1/expenses-reimbursement" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReimbursementOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"entity": "string",
"vendor": "string",
"vendor_status": "string",
"iban": "string"
}
PUT /v1/expenses-reimbursement
Set where you are paid back
Your own bank account for expenses you paid: kept on the vendor you are paid back as (made the first time, numbered as every vendor, and activated by the AI reviewer or a second person). A new IBAN on an existing one is a change of bank details, which waits for a second person as any vendor's; payments wait meanwhile.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | set_reimbursement_v1_expenses_reimbursement_put |
Request body
application/json (required): ReimbursementInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
iban |
string | yes | Your own bank account's IBAN. (min length 15; max length 42) |
Example request
curl -X PUT "$ERP_URL/v1/expenses-reimbursement" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": "CH01",
"iban": "CH93 0076 2011 6238 5295 7"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ReimbursementOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"entity": "string",
"vendor": "string",
"vendor_status": "string",
"iban": "string"
}
POST /v1/expenses/receipts
Claim an expense from a receipt's photo
Read a receipt and keep it as your draft expense, its fields filled by themselves: who was
paid, when, the total and the VAT per rate from the receipt; the category (expense account)
and project from what you booked before; your company card when the receipt or a card line
says so (filled tells how each was found). Check it and submit it with POST /v1/expenses/{id}/submit, or send submit true. 409 (receipt_already_claimed) when the
file is already an expense, 422 (not_a_receipt) for something else, 503 when no language
model is configured. When no model answers in time the receipt is kept all the same, as your
draft with nothing read from it (confidence null), to fill in. Needs an Idempotency-Key.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | claim_receipt_v1_expenses_receipts_post |
Request body
multipart/form-data (required): Body_claim_receipt_v1_expenses_receipts_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The receipt: a photo (JPEG, PNG, WebP) or a PDF; at most 20 MB. |
entity |
string or null | Code of the company it is for; default as POST /v1/expenses. |
|
submit |
boolean | Submit it at once, as read and filled, without a check. (default false) |
Example request
curl -X POST "$ERP_URL/v1/expenses/receipts" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@statement.xml"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
GET /v1/expenses/{expense_id}
Get an expense
The expense with its checks, its bill and whether it is paid back.
| Required scope | read (admin implies every scope) |
| Operation id | get_expense_v1_expenses__expense_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | expense_id |
string (uuid) | yes | Id of the expense (GET /v1/expenses). |
Example request
curl "$ERP_URL/v1/expenses/{expense_id}" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
PATCH /v1/expenses/{expense_id}
Change your draft
Correct what was read or filled before you submit it; each field you change is marked
as yours (filled). 403 (expense_not_yours) for another person's, 409
(expense_not_draft) once submitted.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | update_expense_v1_expenses__expense_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | expense_id |
string (uuid) | yes | Id of the expense (GET /v1/expenses). |
Request body
application/json (required): ExpenseUpdate
| Field | Type | Required | Description |
|---|---|---|---|
merchant |
string or null | Who was paid. (max length 200) | |
merchant_country |
string or null | ISO 3166 country of the merchant. (pattern ^[A-Z]{2}$) |
|
receipt_date |
string (date) or null | The receipt's date. | |
currency |
string or null | ISO 4217. (pattern ^[A-Z]{3}$) |
|
total |
decimal string or null | Paid, VAT included. (greater than 0) | |
vat_lines |
array of VatLine-Input or null |
The VAT printed per rate. (max items 10) | |
account |
string or null | The category: code of the expense account it is booked to. | |
dimensions |
map of string or null | The project and other dimensions, {dimension code: value code}; an empty value takes one away. |
|
description |
string or null | What it was for. (max length 500) | |
paid_with |
"card" | "own" or null |
own (paid back to you) or card (your company card). |
|
card |
string (uuid) or null | Which of your company cards, when you hold several. |
Example request
curl -X PATCH "$ERP_URL/v1/expenses/{expense_id}" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "6640",
"dimensions": {
"project": "P-LYON"
}
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
GET /v1/expenses/{expense_id}/receipt
Download an expense's receipt
The photo or PDF of the receipt, byte for byte (SHA-256 in X-Content-SHA256); 404 for
an expense without one.
| Required scope | read (admin implies every scope) |
| Operation id | expense_receipt_v1_expenses__expense_id__receipt_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | expense_id |
string (uuid) | yes | Id of the expense (GET /v1/expenses). |
| query | download |
boolean | true: as an attachment; default inline. (default false) |
Example request
curl "$ERP_URL/v1/expenses/{expense_id}/receipt" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The receipt as it was sent. | application/pdf, application/xml, text/xml, image/png, image/jpeg, image/webp |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
POST /v1/expenses/{expense_id}/submit
Submit your expense
The AI reviewer checks it against the company's expense policy (receipt kept and
readable, not claimed twice, not too old, within the limit, a project where asked, VAT that
adds up, the card line): every check passes and it is approved and booked at once as a bill
paid back in the next payment run (posted); one warns and it waits in Approvals for a
person who may approve and did not pay it (held). In the four_eyes controls mode every
expense waits for such a person. 422 (expense_incomplete) without a total, day or
category.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | submit_expense_v1_expenses__expense_id__submit_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | expense_id |
string (uuid) | yes | Id of the expense (GET /v1/expenses). |
Example request
curl -X POST "$ERP_URL/v1/expenses/{expense_id}/submit" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
POST /v1/expenses/{expense_id}/withdraw
Withdraw your draft
Withdraw a draft you will not claim; it is kept (withdrawn), never deleted, and its
receipt may be claimed again.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | withdraw_expense_v1_expenses__expense_id__withdraw_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | expense_id |
string (uuid) | yes | Id of the expense (GET /v1/expenses). |
Example request
curl -X POST "$ERP_URL/v1/expenses/{expense_id}/withdraw" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ExpenseOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"number": "string",
"person": "string",
"person_name": "string",
"status": "approved",
"source": "demo",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"merchant": "string",
"merchant_country": "string",
"receipt_date": "2026-09-21",
"currency": "string",
"total": "string",
"vat_lines": [
{
"rate": "string",
"net": "string",
"vat": "string"
}
],
"account": "string",
"account_name": "string",
"dimensions": {},
"description": "string",
"paid_with": "card",
"card": "string",
"card_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"confidence": 1.5,
"filled": {},
"checks": [
{
"name": "string",
"result": "pass",
"detail": "string"
}
],
"review": "string",
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bill_number": "string",
"paid_back": true,
"problem": "string",
"created_at": "2026-09-21T09:30:00Z",
"submitted_at": "2026-09-21T09:30:00Z",
"decided_by": {},
"decided_at": "2026-09-21T09:30:00Z"
}
Schemas
Body_claim_receipt_v1_expenses_receipts_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The receipt: a photo (JPEG, PNG, WebP) or a PDF; at most 20 MB. |
entity |
string or null | Code of the company it is for; default as POST /v1/expenses. |
|
submit |
boolean | Submit it at once, as read and filled, without a check. (default false) |
Body_read_expense_card_statement_v1_expense_cards__card_id__statement_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The issuer's statement: CSV or .xlsx with a date, amount and merchant. |
CardInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
holder |
string | yes | Who holds it: the person's e-mail address or id (user:<id>). |
last_four |
string | yes | The card number's last four digits. (pattern ^[0-9]{4}$) |
currency |
string or null | Its currency; default the company's. (pattern ^[A-Z]{3}$) |
CardLineInput
| Field | Type | Required | Description |
|---|---|---|---|
card |
string (uuid) | yes | The card (GET /v1/expense-cards). |
booked_on |
string (date) | yes | The day the issuer lists. |
amount |
decimal string | yes | Charged, in the card's currency (a refund is negative). |
merchant |
string | yes | As the issuer lists it. (min length 1; max length 200) |
reference |
string or null | The issuer's id. (max length 200) |
CardLineOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the line. |
card_id |
string (uuid) | yes | The card. |
last_four |
string | yes | Its last four digits. |
holder_name |
string | yes | Its holder. |
booked_on |
string (date) | yes | The day the issuer lists. |
amount |
string | yes | Charged, in the card's currency. |
currency |
string | yes | The card's currency. |
merchant |
string | yes | As the issuer lists it. |
reference |
string or null | yes | The issuer's id. |
expense_id |
string (uuid) or null | yes | The expense it is; null: no receipt yet. |
expense_number |
string or null | yes | That expense's number. |
CardLinesInput
| Field | Type | Required | Description |
|---|---|---|---|
lines |
array of CardLineInput |
yes | The payments, as the issuer lists them. (min items 1; max items 5000) |
CardLinesResult
| Field | Type | Required | Description |
|---|---|---|---|
added |
integer | yes | Lines added. |
already_there |
integer | yes | Lines a statement read before already had. |
matched |
integer | yes | Lines matched to a receipt at once. |
lines |
array of CardLineOut |
yes | The lines added. |
CardOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the card. |
entity |
string | yes | Code of the company. |
holder |
string | yes | Its holder (user:<id>). |
holder_name |
string | yes | Their name. |
last_four |
string | yes | The last four digits. |
currency |
string | yes | Its currency. |
vendor |
string | yes | The card's account with its issuer, as a vendor. |
vendor_status |
string | yes | That vendor's status. |
active |
boolean | yes | Still used. |
open_lines |
integer | yes | Its payments without a receipt yet. |
CardUpdate
| Field | Type | Required | Description |
|---|---|---|---|
active |
boolean | yes | False: the card is no longer used. |
ExpenseCheck
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The check: receipt, readable, duplicate, age, ... |
result |
"pass" | "warn" |
yes | warn holds it for a person. |
detail |
string | yes | What the check found, in words. |
ExpenseInput
An expense without a receipt file: a card payment whose receipt is lost, or one typed.
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
card_line |
string (uuid) or null | The company card payment it is (GET /v1/expense-card-lines): its day, amount and merchant are taken from it. |
|
merchant |
string or null | Who was paid. (max length 200) | |
receipt_date |
string (date) or null | When; default today. | |
currency |
string or null | ISO 4217; default the company's. (pattern ^[A-Z]{3}$) |
|
total |
decimal string or null | What was paid, VAT included; needed without a card line. (greater than 0) | |
description |
string or null | What it was for, and why there is no receipt. (max length 500) |
ExpenseOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the expense. |
entity |
string | yes | Code of the company. |
number |
string | yes | EX-2026-000001, gap-free per company and year. |
person |
string | yes | Who paid (user:<id>). |
person_name |
string | yes | Their name. |
status |
"approved" | "draft" | "held" | "posted" | "rejected" | "withdrawn" |
yes | draft (read, to check and submit), held (the AI reviewer or the controls wait for a person), approved (not booked yet: see problem), posted, rejected or withdrawn. |
source |
"demo" | "no_receipt" | "receipt" |
yes | receipt (read from a file), no_receipt (a card payment or ticket without a receipt file) or demo. |
document_id |
string (uuid) or null | yes | The receipt's file (GET /v1/documents). |
merchant |
string or null | yes | Who was paid. |
merchant_country |
string or null | yes | ISO 3166 country of the merchant. |
receipt_date |
string (date) or null | yes | The receipt's date. |
currency |
string | yes | The receipt's currency. |
total |
string or null | yes | Paid, VAT included, in the receipt's currency. |
vat_lines |
array of VatLine-Output |
yes | The VAT printed per rate. |
account |
string or null | yes | The category: the expense account's code. |
account_name |
string or null | yes | The account's name. |
dimensions |
map of string | yes | The project and other dimensions. |
description |
string or null | yes | What it was for. |
paid_with |
"card" | "own" |
yes | own or card. |
card |
string or null | yes | The card's last four digits, when paid by card. |
card_line_id |
string (uuid) or null | yes | The card payment it is matched to. |
confidence |
number or null | yes | 0 to 1: how sure the reading is. |
filled |
map of string | yes | How each field was filled: read (from the receipt), history (as your last ones), default, card (from the card line) or person. |
checks |
array of ExpenseCheck |
yes | What the AI reviewer checked, once submitted. |
review |
string or null | yes | The reviewer's reasoning in words. |
approval_id |
string (uuid) or null | yes | The approval request, once submitted. |
bill_id |
string (uuid) or null | yes | The bill it is booked as, once posted. |
bill_number |
string or null | yes | That bill's number. |
paid_back |
boolean | yes | Paid back to the person (or, by card, settled by the card): nothing open. |
problem |
string or null | yes | Why an approved expense is not booked yet. |
created_at |
string (date-time) | yes | When the receipt came in (UTC). |
submitted_at |
string (date-time) or null | yes | When it was submitted. |
decided_by |
object or null | yes | Who approved or rejected it. |
decided_at |
string (date-time) or null | yes | When. |
ExpenseUpdate
What the person changes on a draft before submitting it (all optional).
| Field | Type | Required | Description |
|---|---|---|---|
merchant |
string or null | Who was paid. (max length 200) | |
merchant_country |
string or null | ISO 3166 country of the merchant. (pattern ^[A-Z]{2}$) |
|
receipt_date |
string (date) or null | The receipt's date. | |
currency |
string or null | ISO 4217. (pattern ^[A-Z]{3}$) |
|
total |
decimal string or null | Paid, VAT included. (greater than 0) | |
vat_lines |
array of VatLine-Input or null |
The VAT printed per rate. (max items 10) | |
account |
string or null | The category: code of the expense account it is booked to. | |
dimensions |
map of string or null | The project and other dimensions, {dimension code: value code}; an empty value takes one away. |
|
description |
string or null | What it was for. (max length 500) | |
paid_with |
"card" | "own" or null |
own (paid back to you) or card (your company card). |
|
card |
string (uuid) or null | Which of your company cards, when you hold several. |
PolicyOut
| Field | Type | Required | Description |
|---|---|---|---|
receipt_limit |
string or null | A receipt above this, in the company's currency, waits for a person; null: no limit. | |
category_limits |
map of string | Lower limits per expense account code, e.g. meals {"6640": "80.00"}. |
|
max_age_days |
integer | A receipt older than this waits for a person. (min 1; max 3660; default 90) |
|
project_over |
string or null | Above this amount an expense names a project or waits; null: never asked. | |
entity |
string | yes | Code of the company. |
currency |
string | yes | The company's currency, of the limits. |
default |
boolean | yes | True: the company has not set its own policy yet. |
PolicyUpdate
| Field | Type | Required | Description |
|---|---|---|---|
receipt_limit |
decimal string or null | A receipt above this, in the company's currency, waits for a person; null: no limit. (greater than 0) | |
category_limits |
map of decimal string | Lower limits per expense account code, e.g. meals {"6640": "80.00"}. |
|
max_age_days |
integer | A receipt older than this waits for a person. (min 1; max 3660; default 90) |
|
project_over |
decimal string or null | Above this amount an expense names a project or waits; null: never asked. (min 0) |
ReimbursementInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string or null | Code (or id) of the company it is for, e.g. CH01; default: the organization's only company, else the one the person's last expense was for. |
|
iban |
string | yes | Your own bank account's IBAN. (min length 15; max length 42) |
ReimbursementOut
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code of the company. |
vendor |
string or null | yes | The vendor you are paid back as (V-00012); null before your first expense. |
vendor_status |
string or null | yes | active, or draft until the AI reviewer or a person activates it. |
iban |
string or null | yes | The account you are paid back to. |
VatLine-Input
| Field | Type | Required | Description |
|---|---|---|---|
rate |
decimal string | yes | The VAT rate printed, in percent (8.1). (min 0; max 100) |
net |
decimal string | yes | The amount before VAT at this rate. |
vat |
decimal string | yes | The VAT printed at this rate. (min 0) |
VatLine-Output
| Field | Type | Required | Description |
|---|---|---|---|
rate |
string | yes | The VAT rate printed, in percent (8.1). |
net |
string | yes | The amount before VAT at this rate. |
vat |
string | yes | The VAT printed at this rate. |