Skip to content

Invoicing

Render a QR-bill

quints invoice invoicing/acme-2026-07.yaml

Writes a Swiss QR-bill PDF (domestic, or export/reverse-charge without QR part) and cross-checks the total against your ledger: if the invoice is already booked at a different amount, you get a conflict, not a silent divergence. If it isn't booked yet, quints prints the draft transaction to paste into books/<year>.bean.

The PDF is filed the way beancount documents are filed — under the income account's folder, date-prefixed, so option "documents" and Fava pick it up:

documents/Income/CH/GmbH/Consulting/External/Domestic/2026-07-02.acme.INV2026014.pdf

The customer part is the registry key from customers.yaml (the slugified name for inline customers). --out overrides the location when you need to.

Rendering the two sample invoices — domestic QR-bill and EUR export — each cross-checked against the ledger

The generated Swiss QR-bill PDF, with receipt and payment part

An invoice is one YAML file:

number: INV2026014
kind: domestic          # or export (reverse charge, no QR part)
currency: CHF
issue_date: 2026-07-02
supply: Juli 2026
customer:
  name: Acme AG
  address:
    - Bahnhofstrasse 1
    - 8001 Zürich
items:
  - description: Consulting — July
    quantity: 1
    unit_price: 1000.00
locale: de_CH

Issuer identity — name, address, VAT ID, bank details per currency, logo — lives once in invoicing/issuer.yaml. Repeat customers can live in invoicing/customers.yaml and be referenced by key (customer: acme).

Bank details

One account per invoicing currency, under bank:

bank:
  CHF:
    iban: CH93 0076 2011 6238 5295 7     # the account invoices are paid into
    bic: POFICHBEXXX
    bank_name: PostFinance AG, Bern      # optional
    # reference: qrr                     # see "Payment reference" below
    # qr_iban: CH57 3000 0123 0008 8901 2
    # qr_reference_id: "123456"          # the six digits your bank assigns you
  EUR:
    iban: DE89 3704 0044 0532 0130 00
    bic: COBADEFFXXX
    bank_name: Commerzbank AG, Köln
    holder: Example GmbH                 # optional — if not the issuer

Every IBAN is checked at load (length, national layout, mod-97 check digits) and every BIC against ISO 9362, so a transposed digit fails before the PDF exists rather than after it was sent.

bic is mandatory for any currency you invoice abroad in. An export invoice refuses to render without one: it goes on the PDF as BIC/SWIFT, next to the beneficiary and the bank's name, so the payer copies it instead of looking one up — a guessed BIC is how a SEPA transfer comes back. quints never derives it from the IBAN: that mapping lives in a bank registry that changes, and a wrong-but-plausible BIC would be worse than none. Ask the bank that holds the account.

To check a pair before it ships — or every account in the issuer config:

quints iban "DE89 3704 0044 0532 0130 00" --bic COBADEFFXXX
quints iban

It reports the country, the institution (IID) for Swiss and Liechtenstein IBANs, and flags a BIC whose country doesn't match the IBAN's — normal for a payment provider (a Wise EUR account is a Belgian IBAN), wrong if you pasted another account's code. It exits non-zero when something is off, so it fits a pre-flight check.

Payment reference

Every invoice asks to be paid with a structured reference, so the payment arrives carrying its own invoice id. Switzerland has two, each tied to a different kind of bank account, and quints picks the Swiss-native one whenever your account can carry it:

QRR — the Swiss QR reference, paid into a QR-IBAN. The payer's bank refuses any payment to a QR-IBAN without a valid reference, so every payment that reaches you is matchable. It is numeric — quints encodes the invoice number so its digits stay visible at the end — and CHF-only. An account with a qr_iban uses it for CHF QR-bills without further ado; add the six-digit identification your bank assigns you, and keep the regular iban for CHF from abroad:

bank:
  CHF:
    qr_iban: CH57 3000 0123 0008 8901 2
    qr_reference_id: "123456"   # quoted: YAML reads a leading zero as octal
    iban: CH93 0076 2011 6238 5295 7

SCOR — the ISO 11649 Creditor Reference, paid into the regular IBAN. RF, two check digits, then the invoice number itself:

RF47 INV2 0260 14

Readable on a bank statement, quotable in a reminder email, valid on a Swiss QR-bill in CHF and EUR and in a SEPA transfer. It is what an account without a qr_iban uses, what every QR-bill in EUR uses, and what every export invoice uses. Prefer it although you have a QR-IBAN? Say so:

bank:
  CHF:
    reference: scor
    qr_iban: CH57 3000 0123 0008 8901 2
    iban: CH93 0076 2011 6238 5295 7

The same invoice under each scheme — the payment part is what the customer scans or retypes. With a QR-IBAN and the bank's identification, a QR reference whose tail is the invoice's own digits:

Payment part paid by QR reference: account CH57 3000 0123 0008 8901 2 (a QR-IBAN), reference 12 34561 00841 17000 00202 60143

With the regular IBAN, a creditor reference that spells the invoice number out:

Payment part paid by creditor reference: account CH93 0076 2011 6238 5295 7, reference RF47 INV2 0260 14

Payment references covers the whole picture — the three types the standard defines, the exact pairing rules with their citations, what the QR reference contains, how to choose, and how a payment finds its way back to the invoice.

The reference is derived from the invoice number and nothing else, which is what lets quints import and quints match credit an incoming payment to its invoice — by the reference it quotes, the plain number, or a QR reference decoded back into one. Two invoices never share a reference as long as their numbers differ in a letter or digit: case and punctuation are not carried, so INV-014 and INV014 would collide (the matcher then reports the payment as ambiguous instead of guessing). A number too long to encode is refused instead of truncated, because a truncated reference is a payment credited to somebody else's invoice. quints invoice prints the reference it used:

ref SCOR RF47 INV2 0260 14

Set reference: on the invoice only to re-issue one that went out with a reference from elsewhere — it is validated by its check digits at load, and refused if it doesn't match the account's scheme.

Your customer's references

Accounts-payable departments pay against their own numbers. customer_reference is their PO or order number; references is the long tail — a Leitweg-ID, a CIG/CUP, a cost centre, a framework contract:

number: INV2026014
customer: acme
customer_reference: PO-2026-118
references:
  - label: Leitweg-ID
    value: 991-12345-67
  - label: Kostenstelle
    value: KST-4711

Both are printed next to the invoice number and date, on domestic and export invoices alike, with customer_reference labelled in the invoice's language ("Ihre Referenz" / "Your reference" / "Su referencia") and the extras labelled exactly as you wrote them. customer_reference also lands in the ledger draft as customer_reference: metadata, so the books can be searched by the number the customer quotes.

On a QR-bill it additionally travels in the structured billing information element, in Swico's S1 syntax:

//S1/10/INV2026014/11/260702/20/PO-2026-118/30/267359056/32/8.1/40/0:30

That is the invoice number, its date, your customer's reference, your UID, the VAT rate and the payment terms — machine-readable for the payer's accounts-payable software. Banks do not forward it with the payment; what comes back to you is the reference and the unstructured message, which carries the invoice number.

Branding

The PDF's typography and palette come from the brand block in issuer.yaml. Three font roles are separate on purpose:

brand:
  font: Geist              # body copy
  font_display: Newsreader # the title
  font_mono: Geist Mono    # every figure, IBAN and reference
  font_dir: invoicing/fonts # optional — ship your own families
  accent: "#6b1f4a"        # title, amount due, leading rules
  logo: invoicing/wordmark.svg
  logo_height: 9           # mm

font_mono is what keeps amount columns aligned on the digit — point it at any monospace (or a face with tabular figures; the template requests them either way). All the values above are also the defaults: the three families ship with quints, so an issuer that configures nothing still renders identically on every machine. Set font_dir to use families of your own; that also switches machine-installed fonts off, so a designer's locally installed variable font can never change how the same invoice renders elsewhere. The full token list (ink, subtle, rule, panel, display weight and stretch) is in the issuer schema.

Foreign invoices

quints invoice invoicing/globex-2026-08.yaml

An export invoice (kind: export) renders without a QR part — it shows the full SEPA/international payment instruction instead (beneficiary, IBAN, BIC/SWIFT, bank, reference) — and defaults to the EU B2B reverse-charge note, which requires the customer's VAT number in the registry. Set reverse_charge: false for customers outside a reverse-charge regime (e.g. US). The currency's account needs both a regular iban (a QR-IBAN can't receive an ordinary credit transfer) and a bic.

The generated export invoice PDF — no QR part, SEPA IBAN and reverse-charge note instead

A project scaffolded with quints init --samples includes both flavours: invoicing/acme-<year>-07.yaml (domestic QR-bill) and invoicing/globex-<year>-08.yaml (EUR export), each tied to a booking in the sample quarter so the cross-check reconciles.

Editor validation

The invoice, issuer, and customers files have JSON Schemas, published with this site at /quints/schema/. Scaffolded YAMLs already carry the matching yaml-language-server: $schema= modeline, so schema-aware editors (and agents) validate fields as you type. To work offline:

quints schema

writes the same schemas to invoicing/schema/.

Who owes you

quints receivables

Open invoices against Receivable:Trade, grouped by invoice id, aged by due date. An invoice disappears from the list when the payment leg is booked with the same ^invoice-id link.

Each invoice shows in its own currency; below the per-currency totals a consolidated total converts everything at the latest rate in your price file (≈ Total CHF). It consolidates in the operating currency by default — --in EUR picks another. A currency with no rate is excluded and called out; quints prices sync fixes that.

quints receivables lists open invoices aged by due date