
LedgerLou Payables
The Payables module covers the entire incoming-invoice process: receive documents by email, extract them via OCR, check against UStG §14, match to vendors, post them, and track open items through to payment.
API ReferenceWorkflow
POST /v1/ocr/extract. In the email inbox, PDF and image attachments are detected automatically.POST /v1/vendors.post_booking, then create the invoice via POST /v1/invoices with line items, vendor ID, intent ID, and document ID. The status changes from draft to open.GET /v1/invoices/open-items shows all unpaid invoices grouped by vendor. Due and overdue items are immediately visible.candidate_kind=‘open_liability’. This reduces the open item and automatically sets the status to partially_paid or paid.Email inbox
Every tenant receives a dedicated inbox address (e.g. lou-orbits-neptune@ledgerlou.de) to which vendors or the internal team can forward documents. The address can be retrieved via GET /v1/inbox/address and is assigned automatically on first call.
What happens on receipt:
- Filter attachments — Only PDF and image attachments (JPEG, PNG) are processed. Other file types are ignored. Maximum 20 attachments per email, max. 10 MB per file.
- Store the document — The attachment is deduplicated by SHA-256 content hash and stored on disk. Duplicates are detected and skipped automatically.
- OCR extraction — The document is converted to Markdown via Mistral OCR, then parsed into structured fields by an LLM (see next section).
- Vendor matching — The extracted data (USt ID number, IBAN, name) is matched against the tenant’s existing vendors.
- Invoice compliance check — The compliance checker verifies UStG §14 mandatory fields and classifies the document type.
- Inbox entry — All results are stored as an inbox entry with status
pending.
Inbox statuses:
| Status | Meaning |
|---|---|
pending | Document received, awaiting processing by user or agent |
processing | Currently being processed |
confirmed | Invoice created and posted |
rejected | Document rejected (invalid, duplicate, or not an invoice document) |
OCR extraction
OCR extraction runs in two stages: first, the document is converted to Markdown text via Mistral OCR. Then an LLM extracts the following structured fields:
Document classification:
| Field | Description |
|---|---|
document_type | Document type: invoice, credit_note, order_confirmation, delivery_note, quote, reminder, receipt, other |
Vendor data:
| Field | Description |
|---|---|
vendor_name | Name of the vendor / issuer |
vendor_address | Full address of the vendor |
vendor_vat_id | USt ID number (e.g. DE123456789) |
vendor_tax_id | Tax number (e.g. 27/123/45678) |
vendor_iban | Vendor’s IBAN |
Buyer data:
| Field | Description |
|---|---|
buyer_name | Name of the recipient of the supply |
buyer_address | Address of the recipient of the supply |
Invoice data:
| Field | Description |
|---|---|
invoice_number | Invoice number |
invoice_date | Invoice date (YYYY-MM-DD) |
delivery_date | Delivery / service date (YYYY-MM-DD) |
due_date | Due date (YYYY-MM-DD) |
total_net | Net amount |
total_tax | VAT amount |
total_gross | Gross amount |
currency | Currency (default: EUR) |
Line items:
| Field | Description |
|---|---|
line_items[].description | Description of the line item |
line_items[].quantity | Quantity |
line_items[].unit_price | Net unit price |
line_items[].tax_rate | Tax rate as a decimal (e.g. 0.19) |
line_items[].line_total | Line-item gross amount |
Other:
| Field | Description |
|---|---|
payment_terms | Payment terms |
reference_text | Reference text / memo for the transfer |
Fields that cannot be detected in the document are returned as null. Tax rates that the LLM returns as percentages (e.g. 19 instead of 0.19) are normalized automatically.
Invoice compliance check (UStG §14)
After OCR extraction, LedgerLou automatically checks whether the document contains the formal mandatory fields per UStG §14. These fields are prerequisites for input-tax deduction.
What is checked:
| # | Rule | Label |
|---|---|---|
| 1 | supplier_name | Name of the supplier |
| 2 | supplier_address | Address of the supplier |
| 3 | buyer_name | Name of the buyer |
| 4 | buyer_address | Address of the buyer |
| 5 | supplier_tax_id | Tax number or USt ID number |
| 6 | invoice_date | Invoice date |
| 7 | invoice_number | Invoice number |
| 8 | line_items | Quantity and nature of the supply |
| 9 | delivery_date | Date of delivery |
| 10 | net_amount | Net amount |
| 11 | tax_rate_and_amount | Tax rate and tax amount |
| 12 | gross_amount | Gross amount |
Document-type detection: Non-invoice documents (quotes, delivery notes, order confirmations, etc.) are recognized as such. All checks are then marked as not_applicable so that these documents are not mistakenly treated as formally correct invoices.
Delivery date: If no explicit delivery date is present, the invoice date is assumed as the delivery date (common BMF practice). The checker marks this with a corresponding hint.
Result in the dashboard: The “Mandatory fields” column in the inbox table shows at a glance:
- Green
12/12— all mandatory fields present - Yellow
10/12— mandatory fields missing - Red with document type (e.g. “Quote”) — not an invoice document
Note: The invoice compliance check covers the essential formal mandatory fields per UStG §14 and is meant to surface missing fields early. It does not replace tax advice and does not claim to be exhaustive. Edge cases such as intra-Community supplies, reverse-charge cases (§13b UStG), margin-scheme taxation, or industry-specific additional requirements are currently not checked separately. In case of doubt, the invoice should be assessed by a tax advisor.
Document-type detection
The OCR system classifies every incoming document into one of the following types:
| Type | Description |
|---|---|
invoice | Invoice |
credit_note | Credit note |
order_confirmation | Order confirmation |
delivery_note | Delivery note |
quote | Quote |
reminder | Reminder |
receipt | Receipt |
other | Other |
Only invoice and credit_note are treated as invoice documents and checked against the mandatory fields. For all other document types, all checks are marked as not_applicable.
JSON schema (compliance_check)
The check result is stored as a compliance_check JSONB field on the inbox entry and is retrievable via GET /v1/inbox, GET /v1/inbox/:id, and the MCP tools list_inbox_documents and get_inbox_document.
{
"document_type": "invoice",
"is_invoice": true,
"compliant": true,
"passed": 12,
"total": 12,
"checks": [
{
"rule": "supplier_name",
"label": "Name des Leistenden",
"status": "pass"
},
{
"rule": "delivery_date",
"label": "Zeitpunkt der Lieferung",
"status": "pass",
"detail": "Rechnungsdatum als Leistungsdatum angenommen (BMF-Praxis)"
}
]
}
Every check has one of three status values:
pass— mandatory field presentfail— mandatory field missingnot_applicable— check not applicable (non-invoice document)
Example: invoice with missing fields
{
"document_type": "invoice",
"is_invoice": true,
"compliant": false,
"passed": 10,
"total": 12,
"checks": [
{ "rule": "supplier_name", "label": "Name des Leistenden", "status": "pass" },
{ "rule": "supplier_address", "label": "Anschrift des Leistenden", "status": "pass" },
{ "rule": "buyer_name", "label": "Name des Leistungsempfängers", "status": "fail" },
{ "rule": "buyer_address", "label": "Anschrift des Leistungsempfängers", "status": "fail" },
{ "rule": "supplier_tax_id", "label": "Steuernummer oder USt-IdNr.", "status": "pass", "detail": "USt-IdNr. vorhanden" },
{ "rule": "invoice_date", "label": "Rechnungsdatum", "status": "pass" },
{ "rule": "invoice_number", "label": "Rechnungsnummer", "status": "pass" },
{ "rule": "line_items", "label": "Menge und Art der Leistung", "status": "pass" },
{ "rule": "delivery_date", "label": "Zeitpunkt der Lieferung", "status": "pass", "detail": "Rechnungsdatum als Leistungsdatum angenommen (BMF-Praxis)" },
{ "rule": "net_amount", "label": "Nettobetrag", "status": "pass" },
{ "rule": "tax_rate_and_amount", "label": "Steuersatz und Steuerbetrag", "status": "pass" },
{ "rule": "gross_amount", "label": "Bruttobetrag", "status": "pass" }
]
}
Example: non-invoice document (quote)
{
"document_type": "quote",
"is_invoice": false,
"compliant": false,
"passed": 0,
"total": 0,
"checks": [
{ "rule": "supplier_name", "label": "Name des Leistenden", "status": "not_applicable", "detail": "Kein Rechnungsdokument" },
{ "rule": "supplier_address", "label": "Anschrift des Leistenden", "status": "not_applicable", "detail": "Kein Rechnungsdokument" }
]
}
Vendor matching
When a document arrives by email, LedgerLou tries to match the vendor from the OCR data to an existing payable automatically. Matching runs in three stages:
Stage 1 — USt ID number (highest priority)
If OCR extracted a USt ID number, a vendor with an identical USt ID number is searched. Matches are considered certain (confidence: 1.0).
Stage 2 — IBAN
If no USt ID number was found or no match exists, the extracted IBAN is matched against the IBAN fields of existing vendors.
Stage 3 — Name (fuzzy match)
If neither USt ID number nor IBAN leads to a match, the extracted vendor name is compared against existing vendors. A match is only proposed if similarity is above a threshold.
The result is stored as vendor_match on the inbox entry:
{
"status": "matched",
"vendor_id": "9fa85f64-...",
"vendor_name": "Hetzner Online GmbH",
"confidence": 1.0,
"match_method": "vat_id"
}
With status: "not_found", no matching vendor was found — in this case a new vendor must be created before posting.
Duplicate detection
The Payables module prevents duplicate entries on several levels:
Vendors
When creating a vendor via POST /v1/vendors, the following are checked:
- Name (case-insensitive, Unicode-normalized) — an identical name is rejected
- USt ID number — an identical USt ID number is rejected
- Tax number — an identical tax number is rejected
The error response contains the ID of the existing vendor, so that an agent or user can refer to it directly.
Invoices
When creating an incoming invoice via POST /v1/invoices, the combination of vendor ID and invoice number is checked for uniqueness. A duplicate invoice number for the same vendor is rejected.
Inbox documents
On email receipt, the content hash (SHA-256) of each document is computed. If the same document is received again (e.g. by multiple forwards), the duplicate entry is silently skipped (ON CONFLICT DO NOTHING).
Status lifecycle (invoices)
| Status | Meaning |
|---|---|
draft | Invoice captured but not yet posted (no intent) |
open | Linked to the journal, fully open |
partially_paid | Partial payment received |
paid | Fully paid |
overdue | Open invoice past its due date |