Documents
Sales invoices, purchase bills and credit notes: draft, submit, void, history; the invoice PDF, ZUGFeRD / Factur-X, sending by e-mail or recording a hand-over; the files of a document: originals, archived outputs and attachments.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/archive/export |
Download the archive of an entity as a ZIP | audit |
|
GET /v1/documents |
List documents | read |
|
POST /v1/documents |
Create an invoice, bill or credit note | draft |
required |
POST /v1/documents/preview |
Preview a document and its VAT | draft |
|
GET /v1/documents/{document_id} |
Get a document | read |
|
PATCH /v1/documents/{document_id} |
Change a draft document | draft |
|
GET /v1/documents/{document_id}/allocations |
List what settled a document | read |
|
GET /v1/documents/{document_id}/dispatches |
List what was sent of an invoice and how it can be sent | read |
|
GET /v1/documents/{document_id}/file |
Download the original file of a document | read |
|
GET /v1/documents/{document_id}/files |
List the files of a document | read |
|
POST /v1/documents/{document_id}/files |
Attach a scanned or received original to a document | draft |
required |
GET /v1/documents/{document_id}/files/{file_id} |
Download a file of a document | read |
|
GET /v1/documents/{document_id}/history |
Get the history of a document | read |
|
POST /v1/documents/{document_id}/mark-sent |
Record that an invoice or credit note was handed over | post |
required |
GET /v1/documents/{document_id}/pdf |
Download an invoice or credit note as PDF | read |
|
POST /v1/documents/{document_id}/reopen |
Reopen a rejected document | draft |
required |
POST /v1/documents/{document_id}/send |
Send an invoice or credit note by e-mail | post |
required |
POST /v1/documents/{document_id}/submit |
Submit a draft document | draft |
|
POST /v1/documents/{document_id}/void |
Void a draft document | draft |
required |
POST /v1/documents/{document_id}/write-off |
Write off what is open on a customer invoice | draft |
required |
POST /v1/documents/{document_id}/write-off/recovery |
Book money received after a write-off | draft |
required |
POST /v1/documents/{document_id}/write-off/vat-correction |
Correct the VAT a write-off kept | draft |
required |
GET /v1/documents/{document_id}/write-offs |
List the write-offs of an invoice | read |
|
GET /v1/entities/{entity}/invoice-content |
What an invoice must show about the seller and the customer | read |
|
GET /v1/entities/{entity}/vat-rate-types |
List the VAT rate types a document line can choose | read |
GET /v1/archive/export
Download the archive of an entity as a ZIP
Every original, archived output and attachment of the entity's posted invoices, bills and
credit notes dated in the range, as a ZIP for an auditor: the files under
files/<document number>/, and a manifest as manifest.csv and manifest.json with one row
per file: the document's number (and the vendor's invoice number of a bill), kind, date,
party and total; the file's name, SHA-256 and source (received, captured,
archived_output, attached); who stored it and when; and its integrity (ok, or
mismatch or unreadable for a file that is then left out of the ZIP). A posted document
without a file has a row with empty file columns.
manifest.json also has expected_files and included_files: the export is complete when
it is in the ZIP and they are equal; a ZIP without manifest.json is incomplete (the
download was cut short).
The ZIP is streamed, one file at a time. Needs the audit scope (the Auditor role) or admin.
The export is an audit event (archive.export) with the entity, the range and the counts.
404 for an unknown entity, 422 when end is before start.
| Required scope | audit (admin implies every scope) |
| Operation id | export_archive_v1_archive_export_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | start |
string (date) | yes | Document date from, inclusive (ISO 8601, 2026-01-01). |
| query | end |
string (date) | yes | Document date to, inclusive (ISO 8601, 2026-12-31). |
Example request
curl "$ERP_URL/v1/archive/export?entity=CH01&start=value&end=value" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | A ZIP with the files and a manifest (manifest.csv, manifest.json). |
application/zip |
| 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 |
GET /v1/documents
List documents
Sales invoices, bills and credit notes of an entity, newest first (by document date, then creation). Each row is a full document with its lines, party code and name, stored original and, once posted, its open amount. All filters combine with AND.
| Required scope | read (admin implies every scope) |
| Operation id | list_documents_v1_documents_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | kind |
array of "purchase_bill" | "purchase_credit_note" | "sales_credit_note" | "sales_invoice" or null |
Repeat the parameter to select several kinds: sales_invoice, sales_credit_note, purchase_bill, purchase_credit_note. Default: all. |
|
| query | status |
array of "draft" | "open" | "paid" | "pending_approval" | "posted" | "rejected" | "void" or null |
Repeat the parameter to select several statuses: draft, pending_approval, posted, rejected, void, open (posted with an open amount) and paid (posted, nothing open). Default: all. |
|
| query | party |
string or null | Customer or vendor code (e.g. C-DE) or id. |
|
| query | currency |
string or null | ISO 4217 code of the document currency, e.g. EUR. |
|
| query | start |
string (date) or null | Document date from, inclusive (ISO 8601, 2026-09-01). |
|
| query | end |
string (date) or null | Document date to, inclusive (ISO 8601, 2026-09-30). |
|
| query | q |
string or null | Text in the number, vendor invoice number, order or buyer reference, notes, or the party's name or code (case-insensitive, any part). | |
| query | limit |
integer | Most documents returned, 1 to 1000. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/documents?entity=CH01" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of DocumentOut |
| 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)
[
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": null
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": null,
"creditable_tax_amount": null,
"creditable_discount_amount": null,
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": null,
"obligation_id": null,
"sales_line_id": null,
"credits_line": null,
"delivery_line_id": null,
"goods_returned": false,
"warehouse_id": null,
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": null,
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": null,
"discount_percent": null,
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": null,
"vat_recovery_percent": "100",
"tax_fx_net_amount": null,
"tax_fx_tax_amount": null,
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
]
POST /v1/documents
Create an invoice, bill or credit note
Create a sales invoice, purchase bill or credit note for a customer or vendor.
Tax is determined per line by data-vNext (place of supply, liable party, rate); the
decision and its legal reference are stored on each line. The response holds the
document and, with submit=true, the submit result: its policy is allow (posted,
numbered) or require_approval (status pending_approval, approval_id set); a
deny answers 403. Refuses with 404 for an unknown entity, party or account, 422 for
invalid lines or a credit note above the open amount of the document it credits, and 409
when submitting for a draft customer or vendor (create with submit=false instead) or
when crediting a document that is not posted. A credit note line with credits_line and a
unit_price crediting a line that had a discount must state its discount (0 for none):
422 credit_discount_required otherwise. A credit note of a bill blocked for payment posts
without settling the bill; the credit stays open until the variance is approved.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | create_document_v1_documents_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| query | submit |
boolean | true (default): also submit the document through the approval policy (it is posted, or waits for approval). false: keep it as a draft. (default true) |
Request body
application/json (required): DocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity the document belongs to, e.g. CH01. |
kind |
"purchase_bill" | "purchase_credit_note" | "sales_credit_note" | "sales_invoice" |
yes | sales_invoice, sales_credit_note, purchase_bill or purchase_credit_note; the sales kinds take a customer as party, the purchase kinds a vendor. |
party |
string | yes | Customer or vendor code (or id). |
document_date |
string (date) or null | Document date (ISO 8601, e.g. 2026-09-15): the invoice date. It must fall in an open period for the document to be posted. It is the tax point unless supply_date moves it; the VAT rates are those of the date of supply (a credit note takes the rates of the document it credits). Sales invoices and credit notes: default today where the company is. Bills and vendor credit notes: required, the date the vendor printed on it (422 document_date_required). |
|
supply_date |
string (date) or null | The date of supply (ISO 8601): when the goods were delivered or the service was performed (Leistungsdatum, date de livraison, data di effettuazione), printed on the invoice; omitted or null: the document date. Where the law of the company's country makes VAT due with the supply (DE, ES, GB; FR and IT for goods), the VAT is reported in the period of the supply, and the VAT rate and the official VAT exchange rate are those of the supply. An invoice issued before the supply (an advance invoice) is reported with its own date. A credit note is reported with its own date whatever its date of supply. Switzerland: VAT is due with the invoice (Art. 40 MWSTG). | |
due_date |
string (date) or null | Due date (ISO 8601). Default: the document date plus the party's payment_terms_days, moved by due_date_convention if given. |
|
currency |
string or null | ISO 4217 code of the document currency, e.g. EUR; default: the party's currency (a credit note takes the currency of the document it credits). (pattern ^[A-Z]{3}$) |
|
external_number |
string or null | Vendor's invoice number. | |
credits |
string (uuid) or null | Document this credit note corrects. | |
notes |
string or null | Free text kept on the document. | |
lines |
array of DocumentLineInput |
The document's lines. At least one, except on a credit note with credits set, which then copies the quantities remaining after posted and pending credits. Price corrections or credits without a line link may require explicit lines. |
|
source_document_id |
string (uuid) or null | Id of the stored original file (e-invoice or captured bill) the document was entered from; shown as source_file and downloadable with GET /v1/documents/{id}/file. |
|
provenance |
object | Free-form object stored with the document, e.g. where the data came from. The server adds the reference-data versions and warnings. On a vendor document, vendor_vat_ids (a list) gives the VAT ids the document shows: the seller's (BT-31) and its tax representative's (BT-63); one of the company's VAT area tells that VAT the vendor printed may be the company's own VAT, not foreign VAT. |
|
payment_iban |
string or null | Bills: the account to pay if not the vendor's IBAN, e.g. a QR-IBAN. | |
payment_reference |
string or null | Bills: the QR reference or ISO 11649 creditor reference to quote. | |
due_date_convention |
"following" | "modified_following" | "modified_preceding" | "preceding" or null |
Roll a due date computed from the payment terms onto a business day of the entity's country payment calendar (CH: SIC, euro area: TARGET), e.g. 'following'. Ignored when due_date is given. | |
buyer_reference |
string or null | Invoices: the buyer's reference (XRechnung BT-10); defaults to the customer's Leitweg-ID. | |
cash_discount_percent |
decimal string or null | Cash discount (Skonto) percent if paid within cash_discount_days, e.g. 2. Default: the party's; 0 for none. (min 0) | |
cash_discount_days |
integer or null | Days from the document date within which the cash discount may be taken, 0 to 365. Give this or cash_discount_date, not both. (min 0; max 365) |
|
cash_discount_date |
string (date) or null | Instead of days. | |
order_reference |
string or null | Buyer's PO number (BT-13). | |
discount_percent |
decimal string or null | A discount on the whole document, in percent of the lines after their own discounts (above 0, at most 100). It is split over the lines by what each is worth, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount in its currency, at most what the lines are worth after their own discounts; split over the lines like discount_percent. On a credit note too, only the document discount it gives applies: the share of the credited document's discount comes with each line that credits a line as it was, in that line's own discount (credits_line). (greater than 0) |
Example request
curl -X POST "$ERP_URL/v1/documents" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"document_date": "2026-09-15",
"entity": "CH01",
"kind": "sales_invoice",
"lines": [
{
"description": "Consulting September 2026",
"quantity": "10",
"unit_price": "180.00"
}
],
"party": "C-CH"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | DocumentResult |
| 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)
{
"document": {
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": null,
"field": null,
"basis": null,
"record_id": null
}
],
"source_file": {
"id": null,
"kind": null,
"filename": null,
"content_type": null,
"size": null,
"sha256": null,
"created_at": null
},
"ic": {
"link_id": null,
"role": null,
"status": null,
"partner_entity": null,
"other_document_id": null
},
"lines": [
{
"line_no": null,
"description": null,
"quantity": null,
"creditable_quantity": null,
"creditable_tax_amount": null,
"creditable_discount_amount": null,
"unit_price": null,
"account_id": null,
"product_id": null,
"obligation_id": null,
"sales_line_id": null,
"credits_line": null,
"delivery_line_id": null,
"goods_returned": null,
"warehouse_id": null,
"as_credited": null,
"supply_type": null,
"rate_type": null,
"tax_category": null,
"use_tax": null,
"vat_scope": null,
"vat_scope_reason": null,
"discount_percent": null,
"discount_amount": null,
"document_discount_amount": null,
"net_amount": null,
"tax_rate": null,
"tax_amount": null,
"tax": null,
"vat_recovery_percent": null,
"tax_fx_net_amount": null,
"tax_fx_tax_amount": null,
"dimensions": null
}
],
"delivery_notes": [
{
"id": null,
"number": null
}
]
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
POST /v1/documents/preview
Preview a document and its VAT
The document the body would create, with the tax of every line determined and its
totals, without storing anything (entry forms show live VAT with it). The body is the
same as for creating a document and is validated the same way (404, 409, 422); the
returned id is a placeholder, status is draft and number is null. For a sales
invoice or credit note missing_content warns of what the entity's or the customer's record
lacks to be a lawful invoice; it does not refuse (printing and sending do).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | preview_document_v1_documents_preview_post |
Request body
application/json (required): DocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity the document belongs to, e.g. CH01. |
kind |
"purchase_bill" | "purchase_credit_note" | "sales_credit_note" | "sales_invoice" |
yes | sales_invoice, sales_credit_note, purchase_bill or purchase_credit_note; the sales kinds take a customer as party, the purchase kinds a vendor. |
party |
string | yes | Customer or vendor code (or id). |
document_date |
string (date) or null | Document date (ISO 8601, e.g. 2026-09-15): the invoice date. It must fall in an open period for the document to be posted. It is the tax point unless supply_date moves it; the VAT rates are those of the date of supply (a credit note takes the rates of the document it credits). Sales invoices and credit notes: default today where the company is. Bills and vendor credit notes: required, the date the vendor printed on it (422 document_date_required). |
|
supply_date |
string (date) or null | The date of supply (ISO 8601): when the goods were delivered or the service was performed (Leistungsdatum, date de livraison, data di effettuazione), printed on the invoice; omitted or null: the document date. Where the law of the company's country makes VAT due with the supply (DE, ES, GB; FR and IT for goods), the VAT is reported in the period of the supply, and the VAT rate and the official VAT exchange rate are those of the supply. An invoice issued before the supply (an advance invoice) is reported with its own date. A credit note is reported with its own date whatever its date of supply. Switzerland: VAT is due with the invoice (Art. 40 MWSTG). | |
due_date |
string (date) or null | Due date (ISO 8601). Default: the document date plus the party's payment_terms_days, moved by due_date_convention if given. |
|
currency |
string or null | ISO 4217 code of the document currency, e.g. EUR; default: the party's currency (a credit note takes the currency of the document it credits). (pattern ^[A-Z]{3}$) |
|
external_number |
string or null | Vendor's invoice number. | |
credits |
string (uuid) or null | Document this credit note corrects. | |
notes |
string or null | Free text kept on the document. | |
lines |
array of DocumentLineInput |
The document's lines. At least one, except on a credit note with credits set, which then copies the quantities remaining after posted and pending credits. Price corrections or credits without a line link may require explicit lines. |
|
source_document_id |
string (uuid) or null | Id of the stored original file (e-invoice or captured bill) the document was entered from; shown as source_file and downloadable with GET /v1/documents/{id}/file. |
|
provenance |
object | Free-form object stored with the document, e.g. where the data came from. The server adds the reference-data versions and warnings. On a vendor document, vendor_vat_ids (a list) gives the VAT ids the document shows: the seller's (BT-31) and its tax representative's (BT-63); one of the company's VAT area tells that VAT the vendor printed may be the company's own VAT, not foreign VAT. |
|
payment_iban |
string or null | Bills: the account to pay if not the vendor's IBAN, e.g. a QR-IBAN. | |
payment_reference |
string or null | Bills: the QR reference or ISO 11649 creditor reference to quote. | |
due_date_convention |
"following" | "modified_following" | "modified_preceding" | "preceding" or null |
Roll a due date computed from the payment terms onto a business day of the entity's country payment calendar (CH: SIC, euro area: TARGET), e.g. 'following'. Ignored when due_date is given. | |
buyer_reference |
string or null | Invoices: the buyer's reference (XRechnung BT-10); defaults to the customer's Leitweg-ID. | |
cash_discount_percent |
decimal string or null | Cash discount (Skonto) percent if paid within cash_discount_days, e.g. 2. Default: the party's; 0 for none. (min 0) | |
cash_discount_days |
integer or null | Days from the document date within which the cash discount may be taken, 0 to 365. Give this or cash_discount_date, not both. (min 0; max 365) |
|
cash_discount_date |
string (date) or null | Instead of days. | |
order_reference |
string or null | Buyer's PO number (BT-13). | |
discount_percent |
decimal string or null | A discount on the whole document, in percent of the lines after their own discounts (above 0, at most 100). It is split over the lines by what each is worth, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount in its currency, at most what the lines are worth after their own discounts; split over the lines like discount_percent. On a credit note too, only the document discount it gives applies: the share of the credited document's discount comes with each line that credits a line as it was, in that line's own discount (credits_line). (greater than 0) |
Example request
curl -X POST "$ERP_URL/v1/documents/preview" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_date": "2026-09-15",
"entity": "CH01",
"kind": "sales_invoice",
"lines": [
{
"description": "Consulting September 2026",
"quantity": "10",
"unit_price": "180.00"
}
],
"party": "C-CH"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentOut |
| 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)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
GET /v1/documents/{document_id}
Get a document
One document with its lines, tax decisions and, once posted, its open amount, by id
or by number (e.g. INV-2026-000001; a number used in several entities answers 409, use
the id). The ETag response header names the version; send it back as If-Match when
you change, submit or void the document. 404 when it does not exist.
| Required scope | read (admin implies every scope) |
| Response header | ETag names the current version |
| Operation id | get_document_v1_documents__document_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentOut, ETag header |
| 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)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
PATCH /v1/documents/{document_id}
Change a draft document
Change a draft: header fields, party and lines (which replace all lines).
Omitted fields keep their value, null clears an optional one. The tax of every line
is determined again. Only drafts change (409 otherwise); a rejected document must be
reopened first (POST .../reopen). Repeating the same change gives the same draft; send
If-Match so a change made in the meantime is not overwritten (412 when the document
is at another version). 422 when the result is invalid, e.g. a negative total.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | update_document_v1_documents__document_id__patch |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed document answers 412. (max length 200) |
Request body
application/json (required): DocumentUpdate
| Field | Type | Required | Description |
|---|---|---|---|
party |
string or null | Customer or vendor code (or id). | |
document_date |
string (date) or null | New document date (ISO 8601); the due date follows unless given. null is refused. |
|
supply_date |
string (date) or null | The date of supply (ISO 8601): when the goods were delivered or the service was performed (Leistungsdatum, date de livraison, data di effettuazione), printed on the invoice; omitted or null: the document date. Where the law of the company's country makes VAT due with the supply (DE, ES, GB; FR and IT for goods), the VAT is reported in the period of the supply, and the VAT rate and the official VAT exchange rate are those of the supply. An invoice issued before the supply (an advance invoice) is reported with its own date. A credit note is reported with its own date whatever its date of supply. Switzerland: VAT is due with the invoice (Art. 40 MWSTG). null clears it (the document date). |
|
due_date |
string (date) or null | New due date (ISO 8601); null recomputes it from the payment terms. |
|
due_date_convention |
"following" | "modified_following" | "modified_preceding" | "preceding" or null |
Moves a due date computed from the payment terms onto a business day of the entity's country calendar (CH: SIC, euro area: TARGET): following, modified_following, preceding or modified_preceding. Ignored when due_date is given. |
|
currency |
string or null | ISO 4217 currency code, e.g. EUR. (pattern ^[A-Z]{3}$) |
|
external_number |
string or null | Vendor's invoice number. | |
notes |
string or null | Free text kept on the document; null clears it. |
|
lines |
array of DocumentLineInput or null |
The new lines; they replace all existing lines (at least one). null or omitted keeps the lines. (min items 1) |
|
payment_iban |
string or null | Bills: the account to pay if it is not the vendor's own IBAN, e.g. a QR-IBAN printed on the bill. It must be one of the vendor's confirmed accounts before a payment to it can be released. | |
payment_reference |
string or null | Bills: the QR reference (27 digits) or ISO 11649 creditor reference (RF...) printed on the bill, quoted when paying. A QR reference needs a QR-IBAN. |
|
buyer_reference |
string or null | XRechnung BT-10. | |
order_reference |
string or null | The buyer's purchase order number (EN 16931 BT-13). | |
cash_discount_percent |
decimal string or null | Cash discount (Skonto) percent; 0 for none. (min 0) | |
cash_discount_days |
integer or null | Days from the document date within which the cash discount may be taken, 0 to 365. Give this or cash_discount_date, not both. (min 0; max 365) |
|
cash_discount_date |
string (date) or null | Last day the cash discount may be taken (ISO 8601, not before the document date), instead of cash_discount_days. |
|
discount_percent |
decimal string or null | A discount on the whole document in percent; it replaces any document discount. null removes the document discount. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount; it replaces any document discount. null removes the document discount. (greater than 0) |
Example request
curl -X PATCH "$ERP_URL/v1/documents/INV-2026-000001" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"due_date": "2026-10-31",
"lines": [
{
"description": "Consulting September 2026",
"quantity": "12",
"unit_price": "180.00"
}
],
"notes": "Payable within 45 days."
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentOut, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
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)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
GET /v1/documents/{document_id}/allocations
List what settled a document
The payments and credit notes that settled the document (or, for a credit note,
what it settled), oldest first. Allocations still waiting for approval are listed with
their status; rejected ones too.
| Required scope | read (admin implies every scope) |
| Operation id | document_allocations_v1_documents__document_id__allocations_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/allocations" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of AllocationOut |
| 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",
"kind": "credit_note",
"status": "string",
"amount": "string",
"source_amount": "string",
"source_currency": "string",
"functional_amount": "string",
"fx_difference": "string",
"created_at": "2026-09-21T09:30:00Z",
"payment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"payment_number": "string",
"payment_date": "2026-09-21",
"payment_status": "string",
"write_off_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"write_off_number": "string",
"payment_journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_number": "string",
"document_kind": "string",
"document_date": "2026-09-21",
"applied_on": "2026-09-21",
"application_journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
GET /v1/documents/{document_id}/dispatches
List what was sent of an invoice and how it can be sent
The sent log of an invoice or credit note (when, to whom, which attachments, the
Message-ID), oldest first, with what a send would do: whether this server sends e-mail
at all (sending_configured), the recipient used without to, allowed_recipients (the
customer's own addresses a send may go to), the default format (pdf) and whether the
XRechnung XML would be attached. 422 for a document that is not a sales
invoice or credit note. The sent log is evidence: it is readable whatever the state of the
customer's or the entity's records, and for a draft it is empty (printing and sending, not
reading, answer 409).
| Required scope | read (admin implies every scope) |
| Operation id | document_dispatches_v1_documents__document_id__dispatches_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/dispatches" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | Dispatches |
| 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)
{
"sending_configured": true,
"test_copy": false,
"suggested_recipient": "string",
"default_format": "pdf",
"allowed_recipients": [
"string"
],
"zugferd_available": true,
"xrechnung_attached": true,
"items": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sent_at": "2026-09-21T09:30:00Z",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"message_id": "string",
"format": "pdf",
"attachments": [
null
],
"sent_by": {}
}
]
}
GET /v1/documents/{document_id}/file
Download the original file of a document
The original a document was entered from: the e-invoice XML or PDF, or the PDF or image read by bill capture. 404 when the document has none.
Shown inline only when its bytes are of an allowed type (PDF, PNG, JPEG, WebP, XML
without HTML or SVG markup); anything else is an application/octet-stream
attachment. Never cached, never embeddable by another site.
| Required scope | read (admin implies every scope) |
| Operation id | document_file_v1_documents__document_id__file_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| query | download |
boolean | true: send as an attachment (download); false (default): inline when the type is safe to show. (default false) |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/file" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The stored original, byte for byte (SHA-256 in X-Content-SHA256). | 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 |
GET /v1/documents/{document_id}/files
List the files of a document
Every file stored for an invoice, bill or credit note, oldest first: the original it
was entered from (received or captured), its archived outputs (archived_output: the
PDF, ZUGFeRD hybrid or XRechnung exactly as they were first output, see GET /v1/documents/{id}/pdf) and the originals attached afterwards (attached). Each file has its
SHA-256, who stored it and when. The list is empty for a document without a file; 404 for an
unknown document.
| Required scope | read (admin implies every scope) |
| Operation id | list_files_v1_documents__document_id__files_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/files" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of FileOut |
| 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",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"source": "archived_output",
"variant": "string",
"stored_by": {},
"stored_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/documents/{document_id}/files
Attach a scanned or received original to a document
Add a scanned or received original to a document of any kind and status: a bill that was
entered by hand, an open receivable or payable the opening import brought in, a credit note.
Send multipart/form-data with the file in the form field file.
Attaching is append-only: nothing is ever replaced or removed, a second file is another
attachment, and each one has its own audit event (document.attach, in the document's
history) with the file's SHA-256. The file is stored under its content, so the same bytes are
stored once. Refused with 422 for an empty file, one over 20 MB or one that is not a PDF or
an image by its bytes, 409 when the document already has this file (as its original or an
attachment), 404 for an unknown document. A retry with the same Idempotency-Key returns the
first answer and attaches nothing more.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | attach_file_v1_documents__document_id__files_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
multipart/form-data (required): Body_attach_file_v1_documents__document_id__files_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The original: a PDF, PNG, JPEG or WebP file, at most 20 MB. Its type is read from its bytes, whatever its name or declared type. |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/files" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@statement.xml"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | FileOut |
| 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",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"source": "archived_output",
"variant": "string",
"stored_by": {},
"stored_at": "2026-09-21T09:30:00Z"
}
GET /v1/documents/{document_id}/files/{file_id}
Download a file of a document
One file of the document: its original, an archived output or an attachment, byte for byte.
Shown inline only when its bytes are of an allowed type (as for GET /v1/documents/{id}/file); never cached, never embeddable by another site. 404 when the
document has no such file, 409 when the stored bytes no longer match their SHA-256. An
archived output is served whatever the state of the customer's records now.
| Required scope | read (admin implies every scope) |
| Operation id | download_file_v1_documents__document_id__files__file_id__get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| path | file_id |
string (uuid) | yes | Id of a stored file (GET /v1/documents/{document_id}/files). |
| query | download |
boolean | true: send as an attachment (download); false (default): inline when the type is safe to show. (default false) |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/files/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The stored file, byte for byte (SHA-256 in X-Content-SHA256). | application/pdf, application/xml, text/xml, image/png, image/jpeg, image/webp, application/octet-stream |
| 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 |
GET /v1/documents/{document_id}/history
Get the history of a document
Audit events of the document, its journal entries (submissions, approvals, rejections, also of earlier attempts) and its stored files (original, archived outputs, attachments), oldest first.
| Required scope | read (admin implies every scope) |
| Operation id | document_history_v1_documents__document_id__history_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/history" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of AuditEventOut |
| 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": 1,
"at": "2026-09-21T09:30:00Z",
"actor": {},
"action": "string",
"object_type": "string",
"object_id": "string",
"request_id": "string",
"idempotency_key": "string",
"data": {}
}
]
POST /v1/documents/{document_id}/mark-sent
Record that an invoice or credit note was handed over
Record that a person handed the invoice or credit note to the customer outside this
system: they e-mailed the downloaded PDF from their own mailbox, posted it, or put it on the
customer's portal. Nothing is sent. The sent log (GET .../dispatches) and the document's
history get a row with the channel, the recipient, the day it was handed over and the
SHA-256 of the archived file that was handed over: file_id names it (an archived output of
GET .../files); omitted, it is the invoice PDF, which is archived now if it was never output
(then the content gate applies as for the PDF). Marking is repeatable: each call is another
hand-over.
Only a person records: the approval policy action send_document is denied to agents by
default (403 policy_denied), and an agent key answers 403 whatever the policy says. 404 for
an unknown document or file_id, 409 when the document is not posted (or, without file_id,
the content gate refuses the PDF), 422 for a document that is not a sales invoice or credit
note, a day before the document's date or in the future. A retry with the same
Idempotency-Key returns the first answer and records nothing more.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | mark_sent_v1_documents__document_id__mark_sent_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): MarkSentInput
| Field | Type | Required | Description |
|---|---|---|---|
channel |
"email" | "other" | "portal" | "post" |
yes | How it was handed over: email (a person sent the downloaded file from their own mailbox), post, portal (the customer's portal) or other. |
recipient |
string | yes | Who got it: an e-mail address, a name or a portal, up to 200 characters. (min length 1; max length 200) |
handed_over_on |
string (date) | yes | The day it was handed over (ISO 8601): not before the document date and not in the future. |
file_id |
string (uuid) or null | The archived output that was handed over (source archived_output in GET /v1/documents/{id}/files). Omitted: the invoice PDF, which is archived now if it was never output. |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/mark-sent" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"handed_over_on": "2026-09-22",
"recipient": "buchhaltung@matter-treuhand.example"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | DispatchOut |
| 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",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sent_at": "2026-09-21T09:30:00Z",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"message_id": "string",
"format": "pdf",
"attachments": [
{
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string"
}
],
"sent_by": {}
}
GET /v1/documents/{document_id}/pdf
Download an invoice or credit note as PDF
The invoice or credit note as an A4 PDF: the entity's name, address, VAT number and
bank account, the customer's address and references (customer number, buyer reference,
purchase order), the lines with VAT, the VAT per rate, the totals, payment terms with a
cash discount, and the notes. The labels and the way numbers and dates are written follow
the customer's language. A Swiss entity's invoice that is not settled gets the Swiss
QR-bill payment part (on the last page, or on a page of its own when it does not fit). In a
test copy every page says it is a test copy and not valid, there is no QR-bill, and
format=zugferd is refused (409, ADR-0039).
Archived as issued. The first time an output is asked for, its exact bytes are stored
with their SHA-256 and linked to the document (GET /v1/documents/{id}/files, source
archived_output). Every later request for the same output (the same format and the
same language, qr_bill and profile, an omitted language being the customer's
language) returns those bytes and renders nothing, so an auditor gets what the customer
got, also after the customer's address was corrected or the invoice was paid. Another
language, qr_bill or profile is another output, archived the same way. A stored
output is returned even when the records lack content the law requires now (the gate
decides the first output only). The first output needs the post scope and an organization
that is not suspended: anyone else (a viewer, an auditor) gets 409 not_issued until a
person who may post has downloaded it; what is archived is read with read. 422 for a
document that is not a sales invoice or credit note (and for format=zugferd of a non-German
entity or an invoice that breaks an EN 16931 rule); 409 when the document is not posted, and
409 invoice_content_incomplete when the entity's or the customer's record lacks content the
law requires on an invoice (name, place or address, VAT number: Art. 26 MWSTG, § 14 UStG):
missing lists each field with its legal basis and the record to complete, and
missing_content on the document says it beforehand.
| Required scope | read (admin implies every scope) |
| Operation id | document_pdf_v1_documents__document_id__pdf_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| query | format |
"pdf" | "zugferd" |
pdf (default): the invoice PDF; zugferd: the hybrid PDF with the EN 16931 invoice embedded as factur-x.xml (ZUGFeRD / Factur-X; German entities). Not yet verified for PDF/A-3: no PDF/A validator runs in CI (backlog B-55). (default "pdf") |
|
| query | profile |
"en16931" | "xrechnung" |
With format=zugferd: the profile of the embedded XML, en16931 (EN 16931, default) or xrechnung (XRechnung 3.0; refused when it breaks an XRechnung rule, e.g. a missing buyer reference). (default "en16931") |
|
| query | language |
"de" | "en" | "fr" | "it" | "sv" or null |
Language of the labels and dates: de, fr, it, en or sv. Omitted: the customer's language, else the language of its country. |
|
| query | qr_bill |
boolean | Swiss entities, format=pdf: add the QR-bill payment part to an invoice that still has an open amount (false leaves it out). (default true) |
|
| query | download |
boolean | true: send as an attachment (download); false (default): inline. (default false) |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/pdf" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The PDF, as archived at its first output (SHA-256 in X-Content-SHA256). | application/pdf |
| 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/documents/{document_id}/reopen
Reopen a rejected document
Return a rejected document to draft, to correct it and submit it again (audited
with the reason). The rejected journal entry stays in the document's history. Only a
rejected document reopens (409 otherwise, also when its vendor invoice number was
recorded again on another bill).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | reopen_document_v1_documents__document_id__reopen_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): DocumentAction
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/reopen" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "Entered twice by mistake"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentOut |
| 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)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
POST /v1/documents/{document_id}/send
Send an invoice or credit note by e-mail
E-mail the invoice or credit note to the customer, with the PDF attached, as archived (see
GET /v1/documents/{id}/pdf; format is pdf by default; zugferd is the hybrid, not yet
verified for PDF/A-3) and, for a German public-sector customer with a Leitweg-ID, the
XRechnung XML next to it. The mail goes to to, which may only name the customer's own
addresses (its e-invoice address, e-mail address and contacts' e-mail addresses), or without
to to the customer's e-invoice or billing address. The answer records what was sent: time,
recipients, subject, Message-ID and the attachments with their SHA-256; the same is in the
document's sent log and in its history.
Only a person sends: the approval policy action send_document is denied to agents by
default (403 policy_denied), and an agent key answers 403 whatever the policy says. 409
when this server has no mail server configured (sending is not configured: download the PDF
instead; sending_configured in GET .../dispatches says so beforehand), in a test
copy (it never e-mails customers, who may be real ones), or when the document is not
posted; 422 when there is no recipient, an address is invalid or is not one of the
customer's (add it to the customer first), or the XML breaks a rule; 409
invoice_content_incomplete when the entity's or the customer's record lacks content the law
requires (as for the PDF); 429 (rate_limited)
beyond the organization's hourly limit of messages (default 60); 502 (mail_failed) when the
mail server does not accept the message, in which case nothing is recorded and the request
can be repeated. A retry with the same Idempotency-Key returns the first answer and sends
nothing.
| Required scope | post (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | send_document_v1_documents__document_id__send_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): SendInput
| Field | Type | Required | Description |
|---|---|---|---|
to |
array of string or null | Recipient addresses, one to five. Each must be an address of the customer: its e-invoice address (scheme EM), its e-mail address or a contact's e-mail address (allowed_recipients of GET /v1/documents/{id}/dispatches); any other address is refused with 422 until it is added to the customer. Omitted: the customer's e-invoice address if it is an e-mail address, else the e-mail address of a contact whose role says billing or accounting, else the customer's e-mail address. (min items 1; max items 5) |
|
message |
string or null | A text of at most 1,000 characters placed before the standard text of the e-mail. (max length 1000) | |
format |
"pdf" | "zugferd" or null |
pdf (default): the invoice PDF; zugferd: the hybrid PDF with the EN 16931 XML inside (German entities only; not yet verified for PDF/A-3, see docs/engineering/backlog.md B-55). |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/send" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"message": "Please quote the order number when you pay.",
"to": [
"buchhaltung@matter-treuhand.example"
]
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | DispatchOut |
| 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 |
| 429 | A per-organization limit was reached, e.g. messages sent per hour (rate_limited, with retry_after_seconds). |
ErrorEnvelope |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sent_at": "2026-09-21T09:30:00Z",
"channel": "email",
"handed_over_on": "2026-09-21",
"recipients": [
"string"
],
"subject": "string",
"message_id": "string",
"format": "pdf",
"attachments": [
{
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string"
}
],
"sent_by": {}
}
POST /v1/documents/{document_id}/submit
Submit a draft document
Post a draft through the approval policy, or queue it for approval. Only drafts
submit (409 otherwise, also when the customer or vendor is still a draft). The submit
result's policy is allow (document posted and numbered) or require_approval
(status pending_approval, a second person decides); deny answers 403. A bill that quotes
a purchase order none of its vendor's in its company waits for a second person whatever the
policy allows; the result's reason says why. A credit note is
checked again against the document it credits: 409 when it now exceeds that document's open
amount, 422 when its lines without an obligation would credit more than the invoice's other
lines left (another credit note took it since the draft). With If-Match, only the version
you read is submitted (412 if it changed since).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | not used |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | submit_document_v1_documents__document_id__submit_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed document answers 412. (max length 200) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/submit" \
-H "Authorization: Bearer $ERP_KEY" \
-H 'If-Match: "1"'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentResult, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
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)
{
"document": {
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": null,
"field": null,
"basis": null,
"record_id": null
}
],
"source_file": {
"id": null,
"kind": null,
"filename": null,
"content_type": null,
"size": null,
"sha256": null,
"created_at": null
},
"ic": {
"link_id": null,
"role": null,
"status": null,
"partner_entity": null,
"other_document_id": null
},
"lines": [
{
"line_no": null,
"description": null,
"quantity": null,
"creditable_quantity": null,
"creditable_tax_amount": null,
"creditable_discount_amount": null,
"unit_price": null,
"account_id": null,
"product_id": null,
"obligation_id": null,
"sales_line_id": null,
"credits_line": null,
"delivery_line_id": null,
"goods_returned": null,
"warehouse_id": null,
"as_credited": null,
"supply_type": null,
"rate_type": null,
"tax_category": null,
"use_tax": null,
"vat_scope": null,
"vat_scope_reason": null,
"discount_percent": null,
"discount_amount": null,
"document_discount_amount": null,
"net_amount": null,
"tax_rate": null,
"tax_amount": null,
"tax": null,
"vat_recovery_percent": null,
"tax_fx_net_amount": null,
"tax_fx_tax_amount": null,
"dimensions": null
}
],
"delivery_notes": [
{
"id": null,
"number": null
}
]
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": null,
"reason": null,
"rule_id": null,
"table_id": null
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
POST /v1/documents/{document_id}/void
Void a draft document
Void a draft: it keeps no number, posts nothing and can no longer change (409 for
any other status; 412 when If-Match names a version that is no longer current). A
vendor invoice number or file of a void bill can be entered again.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| ETag / If-Match | If-Match optional: a changed version answers 412 |
| Response header | ETag names the current version |
| Operation id | void_document_v1_documents__document_id__void_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
| header | If-Match |
string or null | The ETag of the version you read; a changed document answers 412. (max length 200) |
Request body
application/json (required): DocumentAction
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/void" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{
"reason": "Entered twice by mistake"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | DocumentOut, ETag header |
| 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 |
| 412 | If-Match names a version that is no longer current (precondition_failed). |
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)
{
"withholding_code": "string",
"withholding_rate": "string",
"withholding_base": "0.00",
"withholding_amount": "0.00",
"creditable_amount": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party_code": "string",
"party_name": "string",
"party_status": "string",
"number": "string",
"external_number": "string",
"status": "string",
"document_date": "2026-09-21",
"supply_date": "2026-09-21",
"due_date": "2026-09-21",
"currency": "string",
"net_total": "string",
"tax_total": "string",
"total": "string",
"functional_total": "string",
"credits_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"source_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"notes": "string",
"legal_notes": [
"string"
],
"payment_iban": "string",
"payment_reference": "string",
"buyer_reference": "string",
"order_reference": "string",
"open_amount": "string",
"payment_blocked": false,
"tax_fx": {},
"tax_fx_rate": "string",
"tax_fx_net_total": "string",
"tax_fx_tax_total": "string",
"cash_discount_percent": "string",
"cash_discount_date": "2026-09-21",
"cash_discount_amount": "string",
"discount_percent": "string",
"discount_amount": "0.00",
"provenance": {},
"created_by": {},
"created_at": "2026-09-21T09:30:00Z",
"version": 1,
"missing_content": [
{
"party": "buyer",
"field": "name",
"basis": "string",
"record_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"source_file": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "string",
"filename": "string",
"content_type": "string",
"size": 1,
"sha256": "string",
"created_at": "2026-09-21T09:30:00Z"
},
"ic": {
"link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "mirror",
"status": "string",
"partner_entity": "string",
"other_document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"lines": [
{
"line_no": 1,
"description": "string",
"quantity": "string",
"creditable_quantity": "string",
"creditable_tax_amount": "string",
"creditable_discount_amount": "string",
"unit_price": "string",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"obligation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"credits_line": 1,
"delivery_line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"goods_returned": false,
"warehouse_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"as_credited": false,
"supply_type": "string",
"rate_type": "string",
"tax_category": "string",
"use_tax": false,
"vat_scope": "in_scope",
"vat_scope_reason": "damages",
"discount_percent": "string",
"discount_amount": "0.00",
"document_discount_amount": "0.00",
"net_amount": "string",
"tax_rate": "string",
"tax_amount": "string",
"tax": {},
"vat_recovery_percent": "100",
"tax_fx_net_amount": "string",
"tax_fx_tax_amount": "string",
"dimensions": {}
}
],
"delivery_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string"
}
]
}
POST /v1/documents/{document_id}/write-off
Write off what is open on a customer invoice
Write off all or part of what is open on a posted sales invoice the customer will not pay.
An internal entry, not a credit note: nothing goes to the customer. It credits the
receivable and debits the bad-debt account (role bad_debt_expense): with
vat_treatment=correct the output VAT on the amount is corrected too (the loss is final) and
the VAT return shows the reduction like a credit note's; with keep the whole amount goes to
the bad-debt account. A foreign-currency invoice is written off at the rate it is carried at.
It always waits for a second person (submit.status pending_approval, decided on
Approvals); meanwhile it reserves its amount on the invoice. Refused: 404 for an unknown
invoice; 422 when it is not a posted customer invoice (write_off_not_invoice), nothing is
open (write_off_nothing_open), the amount exceeds what is open (more_than_open), the date
is after today (write_off_in_future) or before the invoice or its latest settlement
(write_off_before_settlement), the VAT is to be kept on the cash basis
(write_off_keep_cash_basis: no VAT is owed on money never received), or the chart has no
bad-debt account (role_missing); 409
when the period is closed or the customer is blocked.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | write_off_v1_documents__document_id__write_off_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): WriteOffInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_date |
string (date) | yes | The day of the write-off (ISO 8601): on or after the invoice and its latest settlement, not after today, in an open period. |
amount |
decimal string or null | What to write off, VAT included, in the invoice's currency; at most what is open. Default: all of what is open. (greater than 0) | |
vat_treatment |
"correct" | "keep" |
yes | correct: the loss is final (bankruptcy closed, nothing more to collect), so the output VAT on the amount is corrected now and the VAT return of the period shows the reduction; keep: the loss is not final yet, the VAT stays owed and the whole amount goes to the bad-debt account (its VAT can be corrected later, once the loss is final). A company that reports VAT when paid (cash basis) always corrects: no VAT is owed on money never received, so keep is refused there (write_off_keep_cash_basis). |
reason |
string | yes | Why the receivable cannot be collected; kept with the write-off and shown to the person who approves it. (min length 1; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/write-off" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": "540.50",
"reason": "Customer bankrupt; the receiver confirmed no dividend.",
"vat_treatment": "correct",
"write_off_date": "2026-10-12"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | WriteOffResult |
| 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)
{
"write_off": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"entity": "string",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_number": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party": "string",
"write_off_date": "2026-09-21",
"currency": "string",
"amount": "string",
"vat_treatment": "correct",
"tax_amount": "string",
"functional_amount": "string",
"reason": "string",
"status": "pending_approval",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-09-21T09:30:00Z",
"recovered": "0.00",
"vat_corrected_later": false,
"follow_ups": [
{
"id": null,
"write_off_id": null,
"kind": null,
"follow_up_date": null,
"currency": null,
"amount": null,
"tax_amount": null,
"bank_account": null,
"note": null,
"status": null,
"journal_entry_id": null,
"created_at": null
}
]
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
POST /v1/documents/{document_id}/write-off/recovery
Book money received after a write-off
The customer paid all or part of a written-off invoice after all. The invoice stays
written off; the entry debits the bank account and credits the bad-debt account (a recovery).
Where the write-off corrected the output VAT (now or later), the VAT is owed again in
proportion to what was received (CH MWSTG Art. 41, DE UStG § 17 Abs. 2): the VAT return of
the period shows it like a sale. Submitted under the approval policy (submit.status
posted or pending_approval). Refused: 404 for an unknown invoice or write-off of it; 422
when the write-off is not posted (write_off_not_posted), the amount is more than what is
written off and not received yet (recovery_more_than_written_off), the date is after today
(follow_up_in_future) or before the write-off (follow_up_before_write_off), or the account
is no bank account (not_a_bank_account); 409 while something else booked on the write-off
waits for approval (write_off_follow_up_waiting) or when the period is closed.
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | write_off_recovery_v1_documents__document_id__write_off_recovery_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): RecoveryInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_id |
string (uuid) | yes | The posted write-off of the invoice the money is for. |
recovery_date |
string (date) | yes | The day the money arrived (ISO 8601): on or after the write-off, not after today, in an open period. |
amount |
decimal string | yes | What was received, VAT included, in the invoice's currency; at most what is written off and not received yet. (greater than 0) |
bank_account |
string or null | Code of the bank account the money arrived on; default: the main bank account for the invoice's currency. | |
note |
string or null | Free text, e.g. where the money came from. (max length 500) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/write-off/recovery" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": "200.00",
"bank_account": "1020",
"note": "First dividend from the bankruptcy office.",
"recovery_date": "2026-11-20",
"write_off_id": "0f6b6a3e-5d7c-4b8e-9a51-2f4c1d0e7a90"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | FollowUpResult |
| 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)
{
"follow_up": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"write_off_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "recovery",
"follow_up_date": "2026-09-21",
"currency": "string",
"amount": "string",
"tax_amount": "string",
"bank_account": "string",
"note": "string",
"status": "pending_approval",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-09-21T09:30:00Z"
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
POST /v1/documents/{document_id}/write-off/vat-correction
Correct the VAT a write-off kept
The loss of a write-off that kept the VAT (vat_treatment=keep) is final now: the output
VAT on what is written off and was not received after all is corrected, dated the day the
loss became final, as a write-off that corrects it at once would have (CH MWSTG Art. 41, DE
UStG § 17 Abs. 2). Its VAT lines go back to the VAT accounts with their tax tags; the
bad-debt account is relieved by as much. Submitted under the approval policy. Refused: 404
for an unknown invoice or write-off of it; 422 when the write-off is not posted
(write_off_not_posted), its VAT is corrected already (write_off_vat_corrected),
everything was received after all (write_off_all_recovered) or the date is after today or
before the write-off (follow_up_in_future, follow_up_before_write_off); 409 while
something else booked on the write-off waits for approval (write_off_follow_up_waiting).
| Required scope | draft (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | write_off_vat_correction_v1_documents__document_id__write_off_vat_correction_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Request body
application/json (required): VatCorrectionInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_id |
string (uuid) | yes | The posted write-off whose VAT was kept, now that the loss is final. |
correction_date |
string (date) | yes | The day the loss became final (ISO 8601): on or after the write-off, not after today, in an open period. |
note |
string or null | Why the loss is final now. (max length 500) |
Example request
curl -X POST "$ERP_URL/v1/documents/INV-2026-000001/write-off/vat-correction" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"correction_date": "2026-12-15",
"note": "Bankruptcy closed; certificate of loss received.",
"write_off_id": "0f6b6a3e-5d7c-4b8e-9a51-2f4c1d0e7a90"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | FollowUpResult |
| 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)
{
"follow_up": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"write_off_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "recovery",
"follow_up_date": "2026-09-21",
"currency": "string",
"amount": "string",
"tax_amount": "string",
"bank_account": "string",
"note": "string",
"status": "pending_approval",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-09-21T09:30:00Z"
},
"submit": {
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "string",
"number": "string",
"policy": {
"decision": "allow",
"reason": "string",
"rule_id": "string",
"table_id": "string"
},
"approval_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "string"
}
}
GET /v1/documents/{document_id}/write-offs
List the write-offs of an invoice
The write-offs of the invoice, oldest first, with their reason, VAT treatment and status (waiting ones and rejected ones too).
| Required scope | read (admin implies every scope) |
| Operation id | document_write_offs_v1_documents__document_id__write_offs_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | document_id |
string | yes | Document id (uuid) or number, e.g. INV-2026-000001. |
Example request
curl "$ERP_URL/v1/documents/INV-2026-000001/write-offs" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of WriteOffOut |
| 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",
"number": "string",
"entity": "string",
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"document_number": "string",
"party_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"party": "string",
"write_off_date": "2026-09-21",
"currency": "string",
"amount": "string",
"vat_treatment": "correct",
"tax_amount": "string",
"functional_amount": "string",
"reason": "string",
"status": "pending_approval",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-09-21T09:30:00Z",
"recovered": "0.00",
"vat_corrected_later": false,
"follow_ups": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"write_off_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"kind": "recovery",
"follow_up_date": "2026-09-21",
"currency": "string",
"amount": "string",
"tax_amount": "string",
"bank_account": null,
"note": null,
"status": "pending_approval",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-09-21T09:30:00Z"
}
]
}
]
GET /v1/entities/{entity}/invoice-content
What an invoice must show about the seller and the customer
The fields of the company profile (seller) and of a customer's record (buyer) that an
invoice of this entity must show by the law of its country, each with its legal basis and
whether every invoice needs it or only some. The same rules decide missing_content on a
document and refuse to print or send one that lacks them.
| Required scope | read (admin implies every scope) |
| Operation id | get_invoice_content_v1_entities__entity__invoice_content_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/invoice-content" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | InvoiceContentOut |
| 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)
{
"country": "string",
"seller": [
{
"field": "name",
"basis": "string",
"always": true
}
],
"buyer": [
{
"field": "name",
"basis": "string",
"always": true
}
]
}
GET /v1/entities/{entity}/vat-rate-types
List the VAT rate types a document line can choose
The rate types the entity's VAT area has in force on the day (Germany has no
super_reduced, France no zero), standard first, each with its rates and what they
apply to (categories), from data-vNext. A line sends the type as rate_type; where a
type has several rates or kinds of exemption (category_needed: France reduced 5.5 % for
books and 10 % for restaurants, the Swiss exemptions), it also sends one of the rate's
categories as tax_category. A line of another type, or one that needs a category and
names none, is refused (tax_rate_type_missing, tax_category_needed).
| Required scope | read (admin implies every scope) |
| Operation id | vat_rate_types_v1_entities__entity__vat_rate_types_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Entity code, e.g. CH01 (GET /v1/entities). |
| query | date |
string (date) or null | The day (ISO 8601) the rates are in force, usually the document date. Defaults to today where the entity is. |
Example request
curl "$ERP_URL/v1/entities/CH01/vat-rate-types" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | VatRateTypes |
| 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)
{
"entity": "string",
"jurisdiction": "string",
"date": "2026-09-21",
"rate_types": [
{
"rate_type": "exempt",
"rates": [
null
],
"category_needed": true
}
]
}
Schemas
AllocationOut
What settled a document, or what a credit note settled.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the allocation. |
kind |
"credit_note" | "credited_document" | "payment" | "write_off" |
yes | payment: a payment or receipt; credit_note: a credit note applied to this document; credited_document: the invoice or bill this credit note settles; write_off: a write-off of the invoice as uncollectable (dated applied_on). |
status |
string | yes | pending (the payment waits for approval), posted, rejected or reversed (the payment was reversed: it settled the document from its date to the reversal's and settles nothing now). |
amount |
string | yes | Amount applied to the document, in the document currency. |
source_amount |
string or null | The same settlement on the other side, in its own currency (source_currency): what the payment received or paid for it, or what the credit note (or the credited document) took. Null when that side is in this document's currency. |
|
source_currency |
string or null | The currency of the payment, credit note or credited document, when it is not this document's. | |
functional_amount |
string | yes | This document's carrying amount relieved, in the functional currency (on a credit note applied later, the credit note's own, which may differ from the document's it settled). |
fx_difference |
string | yes | Share of the realised FX gain (positive) or loss (negative), in the functional currency. |
created_at |
string (date-time) | yes | When the allocation was created (UTC). |
payment_id |
string (uuid) or null | Payment allocations: id of the payment or receipt. | |
payment_number |
string or null | Payment allocations: its number, e.g. PAY-2026-000001. |
|
payment_date |
string (date) or null | Payment allocations: its date. | |
payment_status |
string or null | Payment allocations: its status. | |
write_off_id |
string (uuid) or null | A write-off: its id. | |
write_off_number |
string or null | A write-off: its number. | |
payment_journal_entry_id |
string (uuid) or null | Payment allocations: its journal entry, once posted. | |
document_id |
string (uuid) or null | The credit note, or the credited document. | |
document_number |
string or null | Number of the credit note or credited document. | |
document_kind |
string or null | Kind of that document. | |
document_date |
string (date) or null | Date of that document. | |
applied_on |
string (date) or null | An application made later (ADR-0045): the day it took effect, not the payment's or credit note's own date. | |
application_journal_entry_id |
string (uuid) or null | An application made later: its own journal entry, once posted (the payment's entry booked the amount on account, not this settlement). |
AttachmentOut
| Field | Type | Required | Description |
|---|---|---|---|
filename |
string | yes | File name of the attachment, e.g. INV-2026-000001.pdf. |
content_type |
string | yes | Media type, e.g. application/pdf. |
size |
integer | yes | Size in bytes. |
sha256 |
string | yes | SHA-256 of the attached bytes, hex. |
Body_attach_file_v1_documents__document_id__files_post
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The original: a PDF, PNG, JPEG or WebP file, at most 20 MB. Its type is read from its bytes, whatever its name or declared type. |
DispatchOut
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the dispatch. |
document_id |
string (uuid) | yes | Id of the invoice or credit note that was sent. |
sent_at |
string (date-time) | yes | When the mail server accepted the message, or when a person recorded the hand-over (UTC). |
channel |
"email" | "other" | "portal" | "post" | "system_email" |
yes | system_email (this server sent it) or what a person recorded: email, post, portal, other. |
handed_over_on |
string (date) or null | yes | The day a person says it was handed over; null for a message of this server. |
recipients |
array of string | yes | The addresses it was sent to, or the recipient a person recorded. |
subject |
string or null | yes | Subject line of the e-mail; null for a recorded hand-over. |
message_id |
string or null | yes | The Message-ID header, to find the mail in a mailbox; null for a recorded hand-over. |
format |
"pdf" | "xrechnung" | "zugferd" |
yes | pdf, zugferd or xrechnung: the form of the file handed over. |
attachments |
array of AttachmentOut |
yes | What was handed over, with each file's SHA-256: for a message of this server the PDF and, for a German public-sector customer with a Leitweg-ID, the XRechnung XML; for a recorded hand-over the archived file. |
sent_by |
object | yes | The actor (type, id) that sent it. |
Dispatches
How an invoice can be delivered, and what was already sent.
| Field | Type | Required | Description |
|---|---|---|---|
sending_configured |
boolean | yes | false when this deployment has no mail server, or this is a test copy: POST .../send answers 409, and the document is delivered by downloading the PDF. |
test_copy |
boolean | true in a test copy (POST /v1/test-copies): it never e-mails customers, who may be real ones copied from the organization it was made from. (default false) |
|
suggested_recipient |
string or null | yes | The address a send without to would use; null when the customer has none. |
default_format |
"pdf" | "zugferd" |
yes | The format a send without format uses: pdf (ZUGFeRD stays available on request until the hybrid is validated as PDF/A-3). |
allowed_recipients |
array of string | yes | The addresses a send may go to: the customer's e-invoice address, e-mail address and contacts' e-mail addresses. Add an address to the customer to use it. |
zugferd_available |
boolean | yes | true when the invoice can be output as ZUGFeRD / Factur-X (German entity). |
xrechnung_attached |
boolean | yes | true when a send also attaches the XRechnung XML (German entity, customer with a Leitweg-ID). |
items |
array of DispatchOut |
yes | What was sent, oldest first. |
DocumentAction
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | yes | Why; kept in the audit trail. (min length 1; max length 500) |
DocumentInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the entity the document belongs to, e.g. CH01. |
kind |
"purchase_bill" | "purchase_credit_note" | "sales_credit_note" | "sales_invoice" |
yes | sales_invoice, sales_credit_note, purchase_bill or purchase_credit_note; the sales kinds take a customer as party, the purchase kinds a vendor. |
party |
string | yes | Customer or vendor code (or id). |
document_date |
string (date) or null | Document date (ISO 8601, e.g. 2026-09-15): the invoice date. It must fall in an open period for the document to be posted. It is the tax point unless supply_date moves it; the VAT rates are those of the date of supply (a credit note takes the rates of the document it credits). Sales invoices and credit notes: default today where the company is. Bills and vendor credit notes: required, the date the vendor printed on it (422 document_date_required). |
|
supply_date |
string (date) or null | The date of supply (ISO 8601): when the goods were delivered or the service was performed (Leistungsdatum, date de livraison, data di effettuazione), printed on the invoice; omitted or null: the document date. Where the law of the company's country makes VAT due with the supply (DE, ES, GB; FR and IT for goods), the VAT is reported in the period of the supply, and the VAT rate and the official VAT exchange rate are those of the supply. An invoice issued before the supply (an advance invoice) is reported with its own date. A credit note is reported with its own date whatever its date of supply. Switzerland: VAT is due with the invoice (Art. 40 MWSTG). | |
due_date |
string (date) or null | Due date (ISO 8601). Default: the document date plus the party's payment_terms_days, moved by due_date_convention if given. |
|
currency |
string or null | ISO 4217 code of the document currency, e.g. EUR; default: the party's currency (a credit note takes the currency of the document it credits). (pattern ^[A-Z]{3}$) |
|
external_number |
string or null | Vendor's invoice number. | |
credits |
string (uuid) or null | Document this credit note corrects. | |
notes |
string or null | Free text kept on the document. | |
lines |
array of DocumentLineInput |
The document's lines. At least one, except on a credit note with credits set, which then copies the quantities remaining after posted and pending credits. Price corrections or credits without a line link may require explicit lines. |
|
source_document_id |
string (uuid) or null | Id of the stored original file (e-invoice or captured bill) the document was entered from; shown as source_file and downloadable with GET /v1/documents/{id}/file. |
|
provenance |
object | Free-form object stored with the document, e.g. where the data came from. The server adds the reference-data versions and warnings. On a vendor document, vendor_vat_ids (a list) gives the VAT ids the document shows: the seller's (BT-31) and its tax representative's (BT-63); one of the company's VAT area tells that VAT the vendor printed may be the company's own VAT, not foreign VAT. |
|
payment_iban |
string or null | Bills: the account to pay if not the vendor's IBAN, e.g. a QR-IBAN. | |
payment_reference |
string or null | Bills: the QR reference or ISO 11649 creditor reference to quote. | |
due_date_convention |
"following" | "modified_following" | "modified_preceding" | "preceding" or null |
Roll a due date computed from the payment terms onto a business day of the entity's country payment calendar (CH: SIC, euro area: TARGET), e.g. 'following'. Ignored when due_date is given. | |
buyer_reference |
string or null | Invoices: the buyer's reference (XRechnung BT-10); defaults to the customer's Leitweg-ID. | |
cash_discount_percent |
decimal string or null | Cash discount (Skonto) percent if paid within cash_discount_days, e.g. 2. Default: the party's; 0 for none. (min 0) | |
cash_discount_days |
integer or null | Days from the document date within which the cash discount may be taken, 0 to 365. Give this or cash_discount_date, not both. (min 0; max 365) |
|
cash_discount_date |
string (date) or null | Instead of days. | |
order_reference |
string or null | Buyer's PO number (BT-13). | |
discount_percent |
decimal string or null | A discount on the whole document, in percent of the lines after their own discounts (above 0, at most 100). It is split over the lines by what each is worth, so the VAT of each rate goes down by its share. Give this or discount_amount, not both. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount in its currency, at most what the lines are worth after their own discounts; split over the lines like discount_percent. On a credit note too, only the document discount it gives applies: the share of the credited document's discount comes with each line that credits a line as it was, in that line's own discount (credits_line). (greater than 0) |
DocumentResult
| Field | Type | Required | Description |
|---|---|---|---|
document |
DocumentOut |
yes | The document as stored. |
submit |
SubmitResult or null |
yes | How the submission went (policy: allow or require_approval, entry id, approval_id); null when the document was created with submit=false. |
DocumentUpdate
Changes to a draft. Omitted fields keep their value; null clears an optional one.
lines replaces every line. The tax of every line is determined again, also when
only the party or the date changes. Without due_date, a new date or party
recomputes the due date from the payment terms; without currency, a new party
brings its currency.
| Field | Type | Required | Description |
|---|---|---|---|
party |
string or null | Customer or vendor code (or id). | |
document_date |
string (date) or null | New document date (ISO 8601); the due date follows unless given. null is refused. |
|
supply_date |
string (date) or null | The date of supply (ISO 8601): when the goods were delivered or the service was performed (Leistungsdatum, date de livraison, data di effettuazione), printed on the invoice; omitted or null: the document date. Where the law of the company's country makes VAT due with the supply (DE, ES, GB; FR and IT for goods), the VAT is reported in the period of the supply, and the VAT rate and the official VAT exchange rate are those of the supply. An invoice issued before the supply (an advance invoice) is reported with its own date. A credit note is reported with its own date whatever its date of supply. Switzerland: VAT is due with the invoice (Art. 40 MWSTG). null clears it (the document date). |
|
due_date |
string (date) or null | New due date (ISO 8601); null recomputes it from the payment terms. |
|
due_date_convention |
"following" | "modified_following" | "modified_preceding" | "preceding" or null |
Moves a due date computed from the payment terms onto a business day of the entity's country calendar (CH: SIC, euro area: TARGET): following, modified_following, preceding or modified_preceding. Ignored when due_date is given. |
|
currency |
string or null | ISO 4217 currency code, e.g. EUR. (pattern ^[A-Z]{3}$) |
|
external_number |
string or null | Vendor's invoice number. | |
notes |
string or null | Free text kept on the document; null clears it. |
|
lines |
array of DocumentLineInput or null |
The new lines; they replace all existing lines (at least one). null or omitted keeps the lines. (min items 1) |
|
payment_iban |
string or null | Bills: the account to pay if it is not the vendor's own IBAN, e.g. a QR-IBAN printed on the bill. It must be one of the vendor's confirmed accounts before a payment to it can be released. | |
payment_reference |
string or null | Bills: the QR reference (27 digits) or ISO 11649 creditor reference (RF...) printed on the bill, quoted when paying. A QR reference needs a QR-IBAN. |
|
buyer_reference |
string or null | XRechnung BT-10. | |
order_reference |
string or null | The buyer's purchase order number (EN 16931 BT-13). | |
cash_discount_percent |
decimal string or null | Cash discount (Skonto) percent; 0 for none. (min 0) | |
cash_discount_days |
integer or null | Days from the document date within which the cash discount may be taken, 0 to 365. Give this or cash_discount_date, not both. (min 0; max 365) |
|
cash_discount_date |
string (date) or null | Last day the cash discount may be taken (ISO 8601, not before the document date), instead of cash_discount_days. |
|
discount_percent |
decimal string or null | A discount on the whole document in percent; it replaces any document discount. null removes the document discount. (max 100; greater than 0) |
|
discount_amount |
decimal string or null | A discount on the whole document as an amount; it replaces any document discount. null removes the document discount. (greater than 0) |
FileOut
A file stored for a document; the bytes are GET /v1/documents/{id}/files/{file_id}.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the stored file. |
filename |
string | yes | File name, e.g. INV-2026-000001.pdf. |
content_type |
string | yes | Media type, determined from the bytes. |
size |
integer | yes | Size in bytes. |
sha256 |
string | yes | SHA-256 of the file, hex; the download carries it too. |
source |
"archived_output" | "attached" | "captured" | "received" |
yes | Where the file comes from: received (the e-invoice a vendor sent, booked from its data), captured (the PDF or image bill capture read), archived_output (an invoice or credit note exactly as issued: what the customer got) or attached (an original added afterwards). |
variant |
string or null | yes | Of an archived output: which output it is (pdf-auto-qr, zugferd-en16931-auto, xrechnung); null for the other files. |
stored_by |
object | yes | The actor (type, id, name) that stored it. |
stored_at |
string (date-time) | yes | When it was stored (UTC). |
FollowUpOut
Money received after a write-off, or the VAT of a kept write-off corrected later.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the follow-up. |
write_off_id |
string (uuid) | yes | The write-off it follows. |
kind |
"recovery" | "vat_correction" |
yes | recovery (money received after the write-off) or vat_correction (the VAT of a write-off that kept it, corrected once the loss is final). |
follow_up_date |
string (date) | yes | The day of its entry. |
currency |
string | yes | ISO 4217 currency of the invoice and the amounts. |
amount |
string | yes | recovery: what was received; vat_correction: the amount, VAT included, whose VAT is corrected (what is written off less what was received before). |
tax_amount |
string | yes | recovery: the output VAT owed again on what was received; vat_correction: the output VAT corrected. |
bank_account |
string or null | yes | recovery: code of the bank account. |
note |
string or null | yes | Free text. |
status |
"pending_approval" | "posted" | "rejected" |
yes | pending_approval (decided on Approvals), posted or rejected. |
journal_entry_id |
string (uuid) | yes | Its journal entry. |
created_at |
string (date-time) | yes | When it was asked for (UTC). |
FollowUpResult
| Field | Type | Required | Description |
|---|---|---|---|
follow_up |
FollowUpOut |
yes | The recovery or the VAT correction. |
submit |
SubmitResult |
yes | How its entry was submitted under the approval policy: posted, or pending_approval with the approval_id a second person decides. |
InvoiceContentOut
What the law requires an invoice of this entity to show about its seller and customers.
| Field | Type | Required | Description |
|---|---|---|---|
country |
string | yes | The entity's country, whose rules apply. |
seller |
array of InvoiceField |
yes | Fields of the company profile. |
buyer |
array of InvoiceField |
yes | Fields of each customer's record. |
InvoiceField
One field of a seller's or customer's record that an invoice of this entity must show.
| Field | Type | Required | Description |
|---|---|---|---|
field |
"name" | "postal_code" | "street" | "tax_id" | "town" | "vat_designation" | "vat_number" |
yes | The field, as in missing_content: name, street, postal_code, town, vat_number, tax_id (the seller's USt-IdNr or Steuernummer) or vat_designation (a Swiss UID with MWST, TVA or IVA). |
basis |
string | yes | The legal basis that makes it mandatory. |
always |
boolean | yes | True: every invoice needs it. False: only some (e.g. an intra-community supply needs the customer's VAT number). |
MarkSentInput
An invoice handed over outside the system.
| Field | Type | Required | Description |
|---|---|---|---|
channel |
"email" | "other" | "portal" | "post" |
yes | How it was handed over: email (a person sent the downloaded file from their own mailbox), post, portal (the customer's portal) or other. |
recipient |
string | yes | Who got it: an e-mail address, a name or a portal, up to 200 characters. (min length 1; max length 200) |
handed_over_on |
string (date) | yes | The day it was handed over (ISO 8601): not before the document date and not in the future. |
file_id |
string (uuid) or null | The archived output that was handed over (source archived_output in GET /v1/documents/{id}/files). Omitted: the invoice PDF, which is archived now if it was never output. |
RecoveryInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_id |
string (uuid) | yes | The posted write-off of the invoice the money is for. |
recovery_date |
string (date) | yes | The day the money arrived (ISO 8601): on or after the write-off, not after today, in an open period. |
amount |
decimal string | yes | What was received, VAT included, in the invoice's currency; at most what is written off and not received yet. (greater than 0) |
bank_account |
string or null | Code of the bank account the money arrived on; default: the main bank account for the invoice's currency. | |
note |
string or null | Free text, e.g. where the money came from. (max length 500) |
SendInput
Who gets the invoice and in which form. Every field is optional.
| Field | Type | Required | Description |
|---|---|---|---|
to |
array of string or null | Recipient addresses, one to five. Each must be an address of the customer: its e-invoice address (scheme EM), its e-mail address or a contact's e-mail address (allowed_recipients of GET /v1/documents/{id}/dispatches); any other address is refused with 422 until it is added to the customer. Omitted: the customer's e-invoice address if it is an e-mail address, else the e-mail address of a contact whose role says billing or accounting, else the customer's e-mail address. (min items 1; max items 5) |
|
message |
string or null | A text of at most 1,000 characters placed before the standard text of the e-mail. (max length 1000) | |
format |
"pdf" | "zugferd" or null |
pdf (default): the invoice PDF; zugferd: the hybrid PDF with the EN 16931 XML inside (German entities only; not yet verified for PDF/A-3, see docs/engineering/backlog.md B-55). |
VatCorrectionInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_id |
string (uuid) | yes | The posted write-off whose VAT was kept, now that the loss is final. |
correction_date |
string (date) | yes | The day the loss became final (ISO 8601): on or after the write-off, not after today, in an open period. |
note |
string or null | Why the loss is final now. (max length 500) |
VatRateChoice
| Field | Type | Required | Description |
|---|---|---|---|
series |
string | yes | The rate's series in data-vNext, e.g. fr.vat.reduced. |
rate |
string | yes | Percent, e.g. 5.5; 0 for an exempt supply. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$) |
categories |
array of string | yes | What the rate applies to, in English words, e.g. books. A line names one of them as its tax_category to choose this rate. |
description |
string or null | yes | The legal basis or a remark; null for none. |
VatRateTypeChoice
| Field | Type | Required | Description |
|---|---|---|---|
rate_type |
"exempt" | "parking" | "reduced" | "special" | "standard" | "super_reduced" | "zero" |
yes | The rate type a line sends as rate_type. |
rates |
array of VatRateChoice |
yes | The rates of the type in force, at least one. |
category_needed |
boolean | yes | True when the type has several rates (France: reduced 5.5 % and 10 %) or several kinds of exemption (Switzerland): a line of this type must name its tax_category, else the document is refused with tax_category_needed. |
VatRateTypes
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Entity code. |
jurisdiction |
string | yes | Whose rates these are: the entity's VAT area (Liechtenstein: CH), or its province where it has one (CA-ON). |
date |
string (date) | yes | The day the rates are in force. |
rate_types |
array of VatRateTypeChoice |
yes | Only the rate types the jurisdiction has on that day, standard first; empty where data-vNext has no rate for it. |
WriteOffInput
| Field | Type | Required | Description |
|---|---|---|---|
write_off_date |
string (date) | yes | The day of the write-off (ISO 8601): on or after the invoice and its latest settlement, not after today, in an open period. |
amount |
decimal string or null | What to write off, VAT included, in the invoice's currency; at most what is open. Default: all of what is open. (greater than 0) | |
vat_treatment |
"correct" | "keep" |
yes | correct: the loss is final (bankruptcy closed, nothing more to collect), so the output VAT on the amount is corrected now and the VAT return of the period shows the reduction; keep: the loss is not final yet, the VAT stays owed and the whole amount goes to the bad-debt account (its VAT can be corrected later, once the loss is final). A company that reports VAT when paid (cash basis) always corrects: no VAT is owed on money never received, so keep is refused there (write_off_keep_cash_basis). |
reason |
string | yes | Why the receivable cannot be collected; kept with the write-off and shown to the person who approves it. (min length 1; max length 500) |
WriteOffOut
A write-off of all or part of what was open on a sales invoice.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the write-off. |
number |
string or null | yes | Its number once posted, e.g. WO-2026-000001; none while it waits. |
entity |
string | yes | Code of the company. |
document_id |
string (uuid) | yes | Id of the invoice written off. |
document_number |
string or null | yes | Number of the invoice written off. |
party_id |
string (uuid) | yes | Id of the customer. |
party |
string | yes | Name of the customer. |
write_off_date |
string (date) | yes | The day of the write-off. |
currency |
string | yes | ISO 4217 currency of the invoice and the amounts. |
amount |
string | yes | What was written off, VAT included. |
vat_treatment |
"correct" | "keep" |
yes | correct (the VAT on it was corrected) or keep (the VAT stays owed). |
tax_amount |
string | yes | The output VAT corrected, in the invoice's currency; 0 when kept. |
functional_amount |
string | yes | What it took off the receivable in the functional currency, at the rate the invoice is carried at. |
reason |
string | yes | Why it was written off. |
status |
"pending_approval" | "posted" | "rejected" |
yes | pending_approval (a second person decides it on Approvals; until then it reserves its amount on the invoice), posted or rejected. |
journal_entry_id |
string (uuid) | yes | Its journal entry. |
created_at |
string (date-time) | yes | When it was asked for (UTC). |
recovered |
string | What was received after the write-off (recoveries posted or waiting), in the invoice's currency. (default "0.00") |
|
vat_corrected_later |
boolean | Whether the VAT it kept was corrected later, once the loss was final (a vat_correction follow-up posted or waiting). (default false) |
|
follow_ups |
array of FollowUpOut |
Money received after it and the VAT corrected later, oldest first (rejected ones too). |
WriteOffResult
| Field | Type | Required | Description |
|---|---|---|---|
write_off |
WriteOffOut |
yes | The write-off, waiting for a second person. |
submit |
SubmitResult |
yes | How its entry was submitted: always pending_approval, with the approval_id a second person decides. |