Skip to content

Import statements

Importers never write to your books. The loop:

  1. Import: drafts land in staging/ (gitignored).
  2. Review and complete each draft.
  3. Move it to books/<year>.bean.
  4. Delete the draft.

Nothing reaches the ledger unreviewed.

Applies if

  • Legal form: Einzelfirma, GmbH or AG. Same steps for all three.
  • Sources: UBS (MT940), Yapeal (CSV), Wise and Stripe (API or JSON export).
  • VAT status: any. It only changes how a draft is completed — below.

UBS (MT940)

quints import ubs statements/ubs-2026.mt940

Drafts every movement on the account into staging/. Each draft is flagged:

  • * — complete as drafted (a rule matched and the booking is unambiguous)
  • ! — needs your VAT decision and a linked document before it moves to books

Rules live in quints.toml under [import.ubs] — [payee regex, counter account, flag] triples. See quints.toml.

Yapeal (CSV)

quints import yapeal <statement.csv> drafts a Yapeal account's activity from the CSV export the same way, with rules under [import.yapeal]. The scaffold ships a sample statement (statements/yapeal.csv); enable the importer with quints init --importers ubs,yapeal or by adding the section to quints.toml.

Wise and Stripe

quints import wise --fetch --from 2026-07-01 --to 2026-09-30
quints import stripe --fetch --from 2026-07-01 --to 2026-09-30

--fetch pulls from the APIs: QUINTS_WISE_API_TOKEN (plus QUINTS_WISE_PRIVATE_KEY for SCA-protected profiles) and QUINTS_STRIPE_API_KEY in .env. Both commands also accept already-fetched JSON files as arguments. Wise conversions are merged into one transaction; Stripe fees and payouts are split out, with the VAT inside Stripe's own fees drafted against InputVAT.

Each import writes a new file in staging/, never over an earlier one: a second import the same day goes to <date>-<source>-2.bean. If its drafts are already in an earlier staging file, the import says so; move each to books/ once.

Imports are idempotent — each transaction carries its source id (mt940_ref:, wise_id:, stripe_id:), so re-importing a statement never duplicates a booking. That holds only for what the ledger actually loads: if it has loader errors, the import lists them and warns that a draft may repeat an entry the ledger dropped. Run quints check and fix them first.

Complete a draft

A ! draft knows the cash leg only. Add the counter leg, link the source document (document:), flip ! to *. The VAT legs depend on your status on the transaction date:

Book gross: no InputVAT, no OutputVAT. A foreign service is a plain expense. Bezugsteuer only starts once such purchases pass CHF 10'000 in a calendar year, and then you have to register for it.1

Split a purchase with Swiss VAT into net + InputVAT, and book a sale's VAT to OutputVAT. For a foreign service, book the Bezugsteuer pair. quints vat convert … --bezugsteuer prints it in CHF at the right rate.

Book purchases gross: no InputVAT, ever. The Saldosteuersatz already accounts for input tax. Sales still carry the statutory VAT to OutputVAT. A foreign service still owes Bezugsteuer, as a cost: quints vat convert … --bezugsteuer prints the pair.

quints vat report flags a VAT leg that doesn't fit the method in force on that date.

Payments that quote an invoice

An incoming payment is matched to an open invoice by the reference it carries — the QR reference or RF creditor reference from the QR-bill, or the plain invoice number — read out of the payee, narration and metadata, verified by its check digits, and matched exactly, never by substring. A match turns the draft's counter leg into the receivable clearing, adds invoice: metadata and the ^number link. It is flagged * only when the payment equals what is open on the invoice. A short payment, an overpayment, or a payment with no reference from a customer with open invoices is drafted !, with a ; comment saying how it was allocated: see Allocate customer payments. A reference that fits more than one open invoice matches nothing: the draft stays ! and quints match says why. How the references are built, and which one your QR-bills use, is in Payment references.

Stripe customer invoices

quints import stripe --fetch --from 2026-07-01 --to 2026-09-30 --invoices

--invoices additionally files the invoice PDF Stripe renders for each charge (invoice_pdf) into inbox/, named YYYY-MM-DD.customer.INVOICENUMBER.pdf — the same convention quints invoice files its own PDFs under. That is the document carrying the customer's name, address and tax number, which is what substantiates the place of supply behind an export booking; the payment itself is already evidenced by the stripe_id: reference on the draft.

The key needs Invoices: Read on top of the fetch scopes. Every file is reported on its own line, and anything already in inbox/ or documents/ is left alone, so re-runs download nothing.

Stripe doesn't reliably link an invoice to the balance transaction that pays it, so the two are correlated on amount, currency and timing when no shared id exists. If a charge could belong to more than one invoice, the command says so and files nothing rather than guessing — the drafts are already in staging/ by then, so only the documents are affected.

Stripe's own monthly fee invoices are a separate matter: they have no API. When a month's fee debit carries VAT, the import says which month needs its tax invoice pulled by hand from the Dashboard (Settings → Plans and fees → Invoice history), which the quarterly VAT close needs.

Supplier bills (payables)

A supplier bill is booked when it arrives, not when it is paid: the expense (and any input VAT) against the trade-payables account from quints.toml.

2026-08-20 * "Treuhand Muster" "Bookkeeping — first half" ^TM-2026-4711
  bill: "TM-2026-4711"
  due: 2026-09-19
  Expenses:CH:GmbH:Admin:Bookkeeping   480.00 CHF
  Liabilities:CH:GmbH:Payable:Trade   -480.00 CHF

bill: is the supplier's own invoice number; due: is what the aging is measured against. Both are optional. Without due:, the payment terms run from the bill date — 30 days, or [payables] default_terms_days. A due: that isn't a date (due: end of month) is listed as a problem, and the bill shows without a due date rather than a guessed one. Without bill:, a lone ^link names the bill; with neither, the bill is grouped by payee and amount, which nets against a payment for the same payee and the same amount and nothing else.

The bank payment clears the liability. quints match scores every outgoing draft against the open bills: the supplier's own reference decides it outright, otherwise an amount that fits exactly one open bill, from a payee whose name matches, inside the window after the bill date. An amount that fits two open bills identifies neither — both are listed with the reason and the draft stays flagged for you.

Review helpers

quints match
quints inbox
quints payables

match scores staging drafts and inbox documents against invoices, open supplier bills and existing bookings, so you see what belongs together before you book. inbox inventories inbox/ — filename hints, duplicates, documents already linked. payables ages the supplier bills you still owe, in their original currency plus a consolidated total; --at reports as of a date, --json for an agent. Settled but unpaid VAT is no supplier bill — it sits on PayableVAT — so it is shown as a footer line, not in the table; the detail is in quints vat status.

Drop source PDFs into inbox/ named YYYY-MM-DD.payee.narrative.pdf; once booked, file them under documents/ mirroring the account path, and link them with document: metadata on the transaction.

What quints doesn't do here

  • Book a draft for you. The counter leg, the VAT decision and the document are the review step. An AI agent can do it; quints then checks the result (Working with AI agents).
  • Fetch Stripe's own fee invoices. Stripe has no API for them. The import says which month to download by hand.
  • OCR a PDF. Documents are matched by filename and metadata, not by reading their content.

  1. Art. 45 Abs. 2 Bst. b MWSTG, SR 641.20. ↩