Description
Purpose and use
Invoices manage billing documents from draft through issued, sent, overdue, and paid states. They support line items, VAT, Swiss QR-bill data, PDF generation, OCR extraction, payer details, and prefilled transfer drafts.
Who uses this. Finance operations, billing teams, customer service, payment operations, and customers use invoices when issuing payment documents or preparing payments from received invoices.
How it works. Users create invoice records with line items and payer data, or upload a document for OCR. OCR can identify invoice details, resolve the counterparty, create supporting upload records, and prefill a transfer draft for review.
What users do. Users create or edit drafts, issue and send invoices, upload scanned invoices, check OCR status, review extracted payment data, and mark invoices paid when settlement is confirmed.
Outcomes and side effects. Invoice workflows can create PDFs, uploads, QR payment references, payer records, and draft transfers. Only the transfer or payment workflow moves money; invoice status records the billing lifecycle.
Related manuals: Payers, Transfers, Uploads, PDFGen.
Overview
The Invoices module provides comprehensive functionality for managing invoices, including:
- Creation and management of invoices with line items
- Automatic invoice numbering with configurable formats
- OCR processing of invoice documents
- QR code generation and reading (Swiss QR-bill compatible)
- PDF generation with embedded QR codes
- Management of common receivers and senders
- Support for multiple currencies and VAT calculations
Invoice statuses
Client 2.0 invoices use these MVP statuses:
draft: editable invoice that is not finalised.issued: finalised and valid invoice, not necessarily sent to the customer.sent: invoice was shared or sent to the customer.overdue: invoice due date has passed and the invoice is not paid. The API may return this effective status for issued or sent invoices whose due date is in the past.paid: final successful state.
pending is a legacy alias for issued. New clients should send and display issued; existing pending records and requests are accepted for compatibility and are treated as finalised/valid invoices that are not necessarily sent.
Allowed MVP transitions are:
draft->issuedissued/pending->sentissued/pending/sent/overdue->paid
Only draft invoices can be edited. Non-draft invoices only accept lifecycle status transitions. overdue is read-derived only: clients cannot set it directly, and the API returns it when an issued or sent invoice is past due and unpaid. paid is final and cannot transition further.
Endpoints
Create Invoice V3
POST /v3/invoices
Create or update selector-based invoices using source and destination counterparties instead of inline address payloads.
V3 request shape:
source.counterparty_idanddestination.counterparty_idare required.- Optional
cp_account_idmust belong to selected counterparty and be active. - Optional
cp_address_idmust be an active address on selected counterparty. If omitted, API picks active preferred billing/fallback address. - Request-side invoice amounts are grouped under
amount, includingamount.currency, and use minor units without repeating precision. - Request-side rate inputs are grouped under
rates. - Item
priceis sent and returned in minor units. - Item
total_priceis returned in minor units. amount.shippingis sent and returned in minor units.rates.discountandrates.vatare rates in percent, not monetary amounts.- Invoice-level response
amountis compact: sharedcurrencyandprecisionare declared once, and each monetary field is returned as a minor-unit string. - Item
priceandtotal_priceare minor-unit strings. Useamount.currency+amount.precisionfor conversion:major = minor / 10^amount.precision.
Request Body:
{
"invoice_number": "INV-V3-000001",
"description": "Monthly services",
"reference": "REF-2024-04",
"status": "draft",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"source": {
"counterparty_id": "11111111-1111-1111-1111-111111111111",
"cp_account_id": "22222222-2222-2222-2222-222222222222",
"cp_address_id": "33333333-3333-3333-3333-333333333333"
},
"destination": {
"counterparty_id": "44444444-4444-4444-4444-444444444444"
},
"amount": {
"currency": "CHF",
"shipping": "1000"
},
"rates": {
"discount": 10,
"vat": 7.70
},
"items": [
{
"title": "Service A",
"description": "Monthly subscription",
"quantity": 1,
"price": "10000"
}
],
"metadata": {
"department": "IT",
"project_code": "P123"
}
}Success Response (201):
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-V3-000001",
"status": "draft",
"source": {
"counterparty_id": "11111111-1111-1111-1111-111111111111",
"cp_account_id": "22222222-2222-2222-2222-222222222222",
"cp_address_id": "33333333-3333-3333-3333-333333333333"
},
"destination": {
"counterparty_id": "44444444-4444-4444-4444-444444444444",
"cp_account_id": null,
"cp_address_id": "66666666-6666-6666-6666-666666666666"
},
"amount": {
"currency": "CHF",
"precision": 2,
"subtotal": "10000",
"shipping": "1000",
"discount_amount": "1000",
"vat_amount": "693",
"total": "10693"
},
"rates": {
"discount": 10,
"vat": 7.7
},
"items": [
{
"id": "987fcdeb-51a2-43d7-9012-345678901234",
"title": "Service A",
"quantity": 1,
"price": "10000",
"total_price": "10000"
}
]
}Interpretation example:
amount.currency = "CHF"amount.precision = 2- item
price = "1050"means CHF 10.50 - item
total_price = "2100"means CHF 21.00
Create Invoice
POST /v2/invoices
Create a new invoice with automatic numbering and PDF generation.
Request Body:
{
"description": "Monthly services",
"status": "draft",
"party_type": "receiver",
"due_date": "2024-04-01T00:00:00Z",
"currency": "CHF",
"iban": "CH93 0076 2011 6238 5295 7",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"name_from": "Sender Company AG",
"address_from": "Bahnhofstrasse 1",
"zip_from": "8001",
"city_from": "Zürich",
"country_from": "Switzerland",
"name_to": "Receiver GmbH",
"address_to": "Hauptstrasse 10",
"zip_to": "3000",
"city_to": "Bern",
"country_to": "Switzerland",
"shipping": 10.00,
"discount": 5.00,
"vat": 7.70,
"items": [
{
"title": "Service A",
"description": "Monthly subscription",
"quantity": 1,
"unit_price": 100.00
}
],
"active": true,
"metadata": {
"department": "IT",
"project_code": "P123"
}
}Success Response (201):
{
"invoice": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-2024-000001",
"status": "draft",
"total": 112.31,
"created_at": "2024-03-21T10:00:00Z"
},
"items": [
{
"id": "987fcdeb-51a2-43d7-9012-345678901234",
"title": "Service A",
"quantity": 1,
"unit_price": 100.00,
"total": 100.00
}
]
}Error Responses:
- 400: Invalid input data
- 401: Unauthorized
- 403: Insufficient permissions
- 500: Internal server error
Process Invoice OCR
POST /v1/invoices/ocr
Extract invoice details from documents using OCR technology.
Request Body:
{
"base64_file": "JVBERi0xLjcKCjEgMCBvYmogICUgZW50...",
"options": {
"language": "en",
"extract_items": true,
"detect_qr": true
}
}Success Response (200):
{
"extracted_data": {
"invoice_number": "INV-2024-000123",
"date": "2024-03-21",
"total": 112.31,
"currency": "CHF",
"iban": "CH93 0076 2011 6238 5295 7",
"recipient": {
"name": "Receiver GmbH",
"address": "Hauptstrasse 10",
"zip": "3000",
"city": "Bern"
},
"items": [
{
"description": "Service A",
"quantity": 1,
"price": 100.00
}
]
},
"confidence_score": 0.95
}Error Responses:
- 400: Invalid file format
- 401: Unauthorized
- 413: File too large
- 422: Processing failed
- 500: Internal server error
Process Invoice OCR V2
POST /v2/invoices/ocr
Extract invoice details from documents using OCR technology with automatic recipient search, draft upload creation, and draft transfer prefilling.
Request Body:
{
"base64_file": "JVBERi0xLjcKCjEgMCBvYmogICUgZW50...",
"customer_id": "550e8400-e29b-41d4-a716-446655440000"
}Success Response (202):
{
"job_id": "123e4567-e89b-12d3-a456-426614174000",
"status": 202,
"upload_id": "123e4567-e89b-12d3-a456-426614174002",
"message": "Job is processing"
}Poll GET /v2/invoices/ocr/{id} for completion. When status is 200, the response includes transfer_id, counterparty_match, counterparty_id, cp_account_id, and result.
Error Responses:
- 400: Invalid input or missing required fields
- 401: Unauthorized
- 403: Insufficient permissions
- 413: File too large
- 500: Internal server error
Check Invoice OCR Status
GET /v1/invoices/ocr/{id}
Check the status of an OCR processing job.
Path Parameters:
- id: Job UUID
Success Response (200):
{
"job_id": "123e4567-e89b-12d3-a456-426614174000",
"status": 200,
"result": {
"total": "112.31",
"customer_name": "Receiver GmbH",
"customer_email": "info@receiver.ch",
"currency_code": "CHF",
"currency_symbol": "CHF",
"due_date": "2024-04-01",
"subtotal": "100.00",
"sales_tax_percentage": "7.7",
"sales_tax_amount": "7.70",
"invoice_number": "INV-2024-000123",
"reference": "REF123",
"bic": "BCGEVBGG",
"iban": "CH93 0076 2011 6238 5295 7"
}
}Error Responses:
- 400: Invalid job ID
- 401: Unauthorized
- 403: Forbidden - job doesn't belong to user
- 404: Job not found
- 500: Internal server error
Check Invoice OCR Status V2
GET /v2/invoices/ocr/{id}
Check the status of an OCR processing job with counterparty destination resolution.
Path Parameters:
- id: Job UUID
Success Response (200):
{
"job_id": "123e4567-e89b-12d3-a456-426614174000",
"status": 200,
"upload_id": "123e4567-e89b-12d3-a456-426614174002",
"transfer_id": "123e4567-e89b-12d3-a456-426614174003",
"counterparty_match": "found",
"counterparty_id": "123e4567-e89b-12d3-a456-426614174001",
"cp_account_id": "123e4567-e89b-12d3-a456-426614174004",
"message": "Job completed",
"result": {
"total": "112.31",
"customer_name": "Receiver GmbH",
"customer_email": "info@receiver.ch",
"currency_code": "CHF",
"currency_symbol": "CHF",
"due_date": "2024-04-01",
"subtotal": "100.00",
"sales_tax_percentage": "7.7",
"sales_tax_amount": "7.70",
"invoice_number": "INV-2024-000123",
"reference": "REF123",
"bic": "BCGEVBGG",
"iban": "CH93 0076 2011 6238 5295 7"
}
}Processing Response (202):
{
"job_id": "123e4567-e89b-12d3-a456-426614174000",
"status": 202,
"upload_id": "123e4567-e89b-12d3-a456-426614174002",
"message": "Job is processing"
}Failed Response (500):
{
"job_id": "123e4567-e89b-12d3-a456-426614174000",
"status": 500,
"message": "Job failed"
}Notes:
upload_ididentifies the draft upload created for the scanned document; present while the job is processing and when complete; omitted when the job fails and draft resources are cleaned uptransfer_ididentifies the draft transfer prefilled from OCR data (only whenstatusis200)counterparty_matchisfoundwhen an existing destination counterparty matched, orcreatedwhen a new one was created (only whenstatusis200)counterparty_idandcp_account_ididentify the resolved destination selectors used by the prefilled draft transfertransaction_idis a legacy customer-transaction draft identifier; new jobs usetransfer_idinsteadrecipientandrecipient_matchare legacy fields for older jobs; new OCR jobs expose counterparty fields instead
Error Responses:
- 400: Invalid job ID
- 401: Unauthorized
- 403: Forbidden - job doesn't belong to user
- 404: Job not found
- 500: Internal server error
Read QR Code
POST /v1/invoices/qrcode
Extract payment information from Swiss QR-bills.
Request Body:
{
"base64_file": "JVBERi0xLjcKCjEgMCBvYmogICUgZW50...",
"options": {
"page": 1,
"validate_format": true
}
}Success Response (200):
{
"qr_info": {
"iban": "CH93 0076 2011 6238 5295 7",
"creditor": {
"name": "Sender Company AG",
"address": "Bahnhofstrasse 1",
"zip": "8001",
"city": "Zürich",
"country": "CH"
},
"amount": 112.31,
"currency": "CHF",
"reference": "RF18539007547034",
"message": "Invoice INV-2024-000123"
}
}Error Responses:
- 400: Invalid file format or no QR code found
- 401: Unauthorized
- 413: File too large
- 422: Invalid QR code format
- 500: Internal server error
Get Invoice By ID
GET /v2/invoices/{id}
Retrieve complete invoice details including line items.
Path Parameters:
- id: Invoice UUID
Query Parameters:
- include_items: Include line items (default: true)
- include_metadata: Include metadata (default: false)
Success Response (200):
{
"invoice": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-2024-000001",
"description": "Monthly services",
"status": "draft",
"party_type": "receiver",
"due_date": "2024-04-01T00:00:00Z",
"currency": "CHF",
"iban": "CH93 0076 2011 6238 5295 7",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"name_from": "Sender Company AG",
"address_from": "Bahnhofstrasse 1",
"zip_from": "8001",
"city_from": "Zürich",
"country_from": "Switzerland",
"name_to": "Receiver GmbH",
"address_to": "Hauptstrasse 10",
"zip_to": "3000",
"city_to": "Bern",
"country_to": "Switzerland",
"subtotal": 100.00,
"shipping": 10.00,
"discount": 5.00,
"vat": 7.70,
"total": 112.31,
"created_at": "2024-03-21T10:00:00Z",
"metadata": {
"department": "IT",
"project_code": "P123"
}
},
"items": [
{
"id": "987fcdeb-51a2-43d7-9012-345678901234",
"invoice_id": "123e4567-e89b-12d3-a456-426614174000",
"title": "Service A",
"description": "Monthly subscription",
"quantity": 1,
"unit_price": 100.00,
"total": 100.00
}
]
}Error Responses:
- 400: Invalid invoice ID format
- 401: Unauthorized
- 403: Insufficient permissions
- 404: Invoice not found
- 500: Internal server error
Get All Invoices
GET /v2/invoices
List all invoices with support for filtering, sorting, and stacking.
Query Parameters:
- status: Filter by invoice status (draft/issued/sent/overdue/paid; legacy pending is accepted as issued)
- date_from: Start date (format: 2024-03-21T00:00:00Z)
- date_to: End date (format: 2024-03-21T23:59:59Z)
- currency: 3-letter currency code (e.g., CHF)
- customer_id: Customer UUID
- limit: Records per page (default: 10, max: 50)
- offset: Pagination offset (default: 0)
- sort: Sort fields with optional - prefix (e.g., -created_at,status)
- stack: Stack by field with optional format (e.g., created_at[DD-MM-YYYY])
Query Parameters Documentation
Search Parameters
Use search.field.operator=value format for filtering:
search.status.eq=draft
search.amount.gt=1000
search.currency.in=CHF,EUR
search.active=trueAvailable operators:
eq: Equal tone: Not equal togt: Greater thangte: Greater than or equal tolt: Less thanlte: Less than or equal toin: In array (comma-separated values)nin: Not in array (comma-separated values)like: Contains string (case-insensitive)ilike: Contains string (case-sensitive)
Sort Parameters
Use sort=field for ascending or sort=-field for descending order:
sort=created_at # ascending
sort=-amount # descending
sort=status,-date # multiple fieldsStack Parameters
Group results by field(s):
stack=status # group by status
stack=currency,status # group by currency then statusPagination
Control the number of results:
limit=25 # items per page (default: 10, max: 100)
offset=50 # skip first 50 itemsExample
/v1/invoices?search.status.in=draft,issued&search.amount.gt=1000&sort=-created_at&stack=status&limit=25This will:
- Find invoices with status 'draft' or 'issued'
- Filter for amounts greater than 1000
- Sort by creation date (newest first)
- Group results by status
- Return up to 25 results per page
Sorting Examples:
sort=created_at # Sort by created_at ascending
sort=-created_at # Sort by created_at descending
sort=-due_date,status # Sort by due_date descending, then status ascendingStacking Examples:
stack=created_at[DD-MM-YYYY] # Group by created_at, format as DD-MM-YYYY
stack=total[#,##0.00] # Group by total, format with thousand separators
stack=status # Group by status as-is
stack=-currency # Group by currency in reverse orderRegular Response (200):
{
"data": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-2024-000001",
"status": "draft",
"total": 112.31,
"created_at": "2024-03-21T10:00:00Z"
}
],
"total": 100,
"has_more": true
}Stacked Response (200):
{
"data": {
"24-03-2024": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-2024-000001",
"status": "draft",
"total": 112.31,
"created_at": "2024-03-24T10:00:00Z"
},
{
"id": "123e4567-e89b-12d3-a456-426614174001",
"invoice_number": "INV-2024-000002",
"status": "paid",
"total": 245.00,
"created_at": "2024-03-24T14:30:00Z"
}
],
"23-03-2024": [
{
"id": "123e4567-e89b-12d3-a456-426614174002",
"invoice_number": "INV-2024-000003",
"status": "issued",
"total": 89.99,
"created_at": "2024-03-23T09:15:00Z"
}
]
},
"total": 100,
"has_more": true
}Error Responses:
- 400: Invalid query parameters
- 401: Unauthorized
- 403: Insufficient permissions
- 500: Internal server error
Update Invoice
PUT /v2/invoices/{id}
Update existing invoice details and items. Only draft invoices can be edited; non-draft invoices only accept valid lifecycle status transitions.
Path Parameters:
- id: Invoice UUID
Request Body:
{
"status": "issued",
"due_date": "2024-04-15T00:00:00Z",
"items": [
{
"id": "987fcdeb-51a2-43d7-9012-345678901234",
"quantity": 2,
"unit_price": 95.00
}
]
}Success Response (200):
{
"invoice": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "issued",
"due_date": "2024-04-15T00:00:00Z",
"total": 209.62,
"updated_at": "2024-03-21T11:00:00Z"
}
}Error Responses:
- 400: Invalid invoice ID or request body
- 401: Unauthorized
- 403: Insufficient permissions
- 404: Invoice not found
- 422: Invalid status transition
- 500: Internal server error
Delete Invoice
DELETE /v1/invoices/{id}
Remove invoice and associated items. Only draft invoices can be deleted.
Path Parameters:
- id: Invoice UUID
Success Response (200):
{
"message": "Invoice successfully deleted",
"id": "123e4567-e89b-12d3-a456-426614174000"
}Error Responses:
- 400: Invalid invoice ID
- 401: Unauthorized
- 403: Insufficient permissions
- 404: Invoice not found
- 422: Cannot delete non-draft invoice
- 500: Internal server error
Get Customer Invoices
GET /v1/invoices/customers/{id}
List all invoices for a specific customer with filtering options.
Path Parameters:
- id: Customer UUID
Query Parameters:
- status: Filter by invoice status (draft/issued/sent/overdue/paid; legacy pending is accepted as issued)
- date_from: Start date (format: 2024-03-21T00:00:00Z)
- date_to: End date (format: 2024-03-21T23:59:59Z)
- currency: 3-letter currency code (e.g., CHF)
Success Response (200):
{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"total_invoices": 50,
"total_amount": 5615.50,
"currency_breakdown": {
"CHF": 4500.00,
"EUR": 1115.50
},
"invoices": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"invoice_number": "INV-2024-000001",
"status": "paid",
"currency": "CHF",
"total": 112.31,
"created_at": "2024-03-21T10:00:00Z"
}
]
}Error Responses:
- 400: Invalid customer ID or query parameters
- 401: Unauthorized
- 403: Insufficient permissions
- 404: Customer not found
- 500: Internal server error
Get Invoice PDF
GET /v1/invoices/{id}/pdf
Generate or retrieve PDF version of an invoice with embedded QR code.
Path Parameters:
- id: Invoice UUID
Query Parameters:
- template: PDF template name (optional, default: "default")
- language: Language code for PDF content (optional, default: "en")
- include_qr: Include QR code in PDF (optional, default: true)
Success Response (200):
{
"content": "JVBERi0xLjcKCjEgMCBvYmogICUgZW50...",
"filename": "INV-2024-000001.pdf",
"content_type": "application/pdf",
"size": 123456
}Error Responses:
- 400: Invalid invoice ID
- 401: Unauthorized
- 403: Insufficient permissions
- 404: Invoice not found
- 422: PDF generation failed
- 500: Internal server error
Get Next Invoice Number
GET /v1/invoices/customers/{id}/next-number
Get the next available invoice number for a customer. The number is reserved for a short period.
Path Parameters:
- id: Customer UUID
Success Response (200):
{
"invoice_number": "INV-20240321-000001",
"expires_at": "2024-03-21T10:05:00Z"
}Error Responses:
- 400: Invalid customer ID
- 401: Unauthorized
- 403: Insufficient permissions
- 500: Internal server error