Appearance
Expense
Expense Types
Receipt: Expenses that are already paid (e.g., credit card purchases, cash payments). These are automatically marked as approved and paid when uploaded, and require a payment method.
Invoice: Unpaid expense invoices from vendors that need to be settled. These start as drafts requiring approval and can have outgoing payments managed through Expense Payments.
Allowed query parameters
Filters
Filter | Type | Description |
---|---|---|
search | String | Full-text search |
type | String | Filter based on type:receipt , invoice |
status | String | Filter based on status:draft , open , approved , overdue , paid |
date_range | DateRange | Filter by issued date<start-date>,<end-date> |
project | Id | Filter by Project Id |
company | Id | Filter by Company Id |
member | Id | Filter by Member Id |
created_by | Id | Filter by creator Member Id |
updated_by | Id | Filter by updater Member Id |
is_locked | Boolean | Show only locked expenses |
is_billable | Boolean | Show only billable expenses |
is_budget_relevant | Boolean | Show only budget relevant expenses |
account_payable | String | Filter by account payable |
tags | Array | Filter by tag IDs |
reconciled | Boolean | Filter by reconciliation status |
exported | Boolean | Filter by export status (ignores false) |
total_range | Range | Filter by total amount range |
net_total_range | Range | Filter by net total amount range |
has_bank_transaction_match | Boolean | Filter by bank transaction match status |
custom_field | Key,String | Filter by Custom Field (eg. my_internal_id,42 ) |
Sorting
name
, created_at
, issued_at
, due_at
, paid_at
, approved_at
, total
, payment_method
Includes
project.company.media
, member.user.media
, createdBy.user.media
, updatedBy.user.media
, tag
, media
, invoice
, tags
, paymentMethod.account
, comments.commentator.media
, payments.bankAccount
, payments.bankTransactionMatch.bankTransaction.bankAccount
, bankTransactionMatch.bankTransaction.bankAccount
, activities
Upload and process an expense
post
/expense/upload
This endpoint allows you to upload a receipt or invoice file (PDF or image) and automatically extract expense data using AI processing, Swiss QR code parsing, and image optimization.
File Upload Process
Before using this endpoint, you need to upload your file using the File Upload process. The uuid
and key
parameters below come from the signed upload URL response.
Attribute (* required) | Type | Description |
---|---|---|
uuid * | String | UUID from the signed upload URL response |
name | String? | Optional expense name |
content_type * | String | File MIME type: application/pdf , image/jpeg , image/jpg , image/png |
key * | String | Storage key from the signed upload URL response (max 10MB) |
data.type * | String | Expense type: receipt or invoice |
data.payment_method_id | String? | Payment method ID (for receipts) |
Processing workflow
- File Upload: The file is validated and stored
- Expense Creation: A basic expense record is created with
is_processing: true
- Background Processing: Three jobs are queued:
- Swiss QR Code Parsing: Extracts payment information from Swiss QR bills
- AI-Powered Extraction: Uses AI to extract expense details (vendor, amounts, dates, etc.)
- Image Optimization: Auto-crops and optimizes the uploaded image
- Completion: When processing finishes,
is_processing
is set tofalse
and an event is broadcast
Receipt vs Invoice behavior
- Receipts: Automatically marked as approved and paid (dates set to current date)
- Invoices: Created as draft expenses requiring manual approval
Example response
json
// HTTP 201 Created
{
"expense": {
"id": "v7rnRjBn9o",
"type": "receipt",
"name": "Restaurant Receipt",
"is_processing": true,
"approved_at": "2024-01-15T00:00:00.000000Z",
"paid_at": "2024-01-15T00:00:00.000000Z"
// ... other expense fields
},
"batch_id": "9c3b5e4d-8f2a-4b6c-9d1e-2f3a4b5c6d7e",
"parsedProperties": [],
"error": null
}
The batch_id
can be used to track the processing status using the Background Jobs monitoring endpoint. Once processing completes, the expense will have extracted data populated and is_processing
will be false
.
Create an expense
post
/expense
Attribute (* required) | Type | Description |
---|---|---|
name * | String | Expense title (max 255 chars) |
description | String? | Expense description |
type | String? | Expense type (invoice , receipt ) |
project_id | String? | A Project Id |
issued_at * | Date | Issue date (must be in valid fiscal year) |
due_at | Date? | Due date (must be after or equal to issued_at) |
approved_at | Date? | Approval date |
paid_at | Date? | Pay date |
biller_address | String? | Creditor address |
biller_iban | String? | Creditor IBAN (validated) |
biller_bic | String? | Creditor BIC (max 11 chars) |
biller_additional_info | String? | Additional biller information |
reference | String? | Payment reference |
account_payable | String? | Account payable |
invoice_id | String? | Associated Invoice Id |
tag_id | String? | A Tag Id |
payment_method_id | String? | Payment method Id |
currency | String? | Currency code |
is_budget_relevant | Boolean? | If expense is budget relevant |
is_billable | Boolean? | If expense is client billable |
is_locked | Boolean? | If expense is locked |
has_acquisition_tax | Boolean? | If expense has acquisition tax |
positions | Array? | An array of position objects (sum must be >= 0) |
positions.*.total * | Float | Position amount |
positions.*.vat_rate * | Float | Position VAT percentage (0-100) |
positions.*.mode * | String | Position mode (inclusive , exclusive ) |
tags | Array? | Array of tag IDs |
custom_fields | Object? | Custom data as Custom Fields |
Example response
json
// HTTP 201 Created
{
// a expense object
}
Update an expense
put
/expense/{id}
Attribute | Type | Description |
---|---|---|
name | String? | Expense title (max 255 chars) |
description | String? | Expense description |
type | String? | Expense type (invoice , receipt ) |
project_id | String? | A Project Id |
issued_at | Date? | Issue date (must be in valid fiscal year) |
due_at | Date? | Due date |
approved_at | Date? | Approval date |
paid_at | Date? | Pay date |
biller_address | String? | Creditor address |
biller_iban | String? | Creditor IBAN (validated) |
biller_bic | String? | Creditor BIC (max 11 chars) |
biller_additional_info | String? | Additional biller information |
reference | String? | Payment reference |
account_payable | String? | Account payable |
payment_method_id | String? | Payment method Id (prohibited for invoice, required for receipt) |
currency | String? | Currency code |
is_budget_relevant | Boolean? | If expense is budget relevant |
is_billable | Boolean? | If expense is client billable |
is_locked | Boolean? | If expense is locked |
is_exported | Boolean? | If expense is exported |
has_acquisition_tax | Boolean? | If expense has acquisition tax |
positions | Array? | An array of position objects (sum must be >= 0) |
positions.*.total * | Float | Position amount |
positions.*.vat_rate * | Float | Position VAT percentage (0-100) |
positions.*.mode | String? | Position mode (inclusive , exclusive ) |
tags | Array? | Array of tag IDs |
extracted_data | Object? | Extracted data object |
extracted_data.type | String? | Extracted expense type |
custom_fields | Object? | Custom data as Custom Fields |
Example response
json
// HTTP 200 OK
{
// a expense object
}
Retrieve an expense
get
/expense/{id}
Example response
json
// HTTP 200 OK
{
"id": "v7rnRjBn9o",
"number": "E-2021-0001",
"type": "invoice",
"project_id": "AG52olvLXP",
"name": "Kreditkartenabrechnung",
"description": "Mastercard",
"issued_at": "2021-07-13 00:00:00",
"due_at": "2021-08-02 00:00:00",
"approved_at": "2021-09-21T23:27:57.000000Z",
"paid_at": null,
"reference": "10 00000 00000 06096 37449 80405",
"account_payable": null,
"amount": 540.05,
"total": 540.05,
"total_with_vat": 540.05,
"tax_total": 0,
"currency": "CHF",
"has_acquisition_tax": false,
"positions": [
{
"mode": "inclusive",
"total": 540.05,
"vat_rate": 0,
"account_no": null
}
],
"total_paid": 0,
"total_open": 540.05,
"biller_uid": null,
"biller_uid_formatted": null,
"biller_address": "Cembra MoneyBank\r\nP.P.\r\n8048 Zürich\r\nCH",
"biller_address_lines": ["Cembra MoneyBank", "P.P.", "8048 Zürich", "CH"],
"biller_address_html": "Cembra MoneyBank<br/>\r\nP.P.<br/>\r\n8048 Zürich<br/>\r\nCH",
"biller_iban": "CH7230114000000018007",
"biller_iban_formatted": "CH72 3011 4000 0000 1800 7",
"biller_bic": null,
"biller_additional_info": null,
"has_qr_iban": true,
"has_qr_reference": true,
"has_scor_reference": false,
"missing_bank_payment_info": [],
"bank_payment_type": "qrr",
"is_paid": false,
"is_approved": true,
"is_budget_relevant": false,
"is_billable": true,
"is_locked": false,
"is_overdue": false,
"is_exported": false,
"is_processing": false,
"days_overdue": 0,
"created_at": "2021-09-22T09:27:20.000000Z",
"updated_at": "2022-04-21T12:46:55.000000Z",
"project": null,
"member": null,
"created_by": null,
"updated_by": null,
"invoice": null,
"receipt": null,
"tag": null,
"tags": [],
"payment_method": null,
"payment_method_id": null,
"payments": [],
"comments": [],
"extracted_data": null,
"activities": [],
"bank_transaction_match": null,
"duplicates": [],
"custom_fields": {}
}
Delete an expense
delete
/expense/{id}
Example response
json
// HTTP 204 No Content
List expenses
get
/expense
The list endpoint accepts the same parameters as in Retrieve an expense and returns a paginated array of the same expense object in the data
property.
Read more about Pagination, Filtering, Sorting and Includes on the Introduction page.
Expense payments
For expense invoices (type invoice
), you can manage outgoing payments to settle the expense using the Expense Payment endpoints. These payments track creditor information, bank details, and execution status for proper payment processing and reconciliation.