Skip to content

Accounting: Billing, Invoicing and Month-End ​

Overview ​

VeriPrompt is the system of record for what your company used and what it owes: usage, package/subscription state, customer master data, contracts, revenue and payment status. It is not meant to be your invoicing or bookkeeping system long-term. The intended workflow is:

  1. Export the fields your external accounting/invoicing system needs — customer master data, DATEV or generic journal postings, aged receivables, invoice lists — from the screens below.
  2. Invoice, dun and reconcile in your own accounting software, using those exports as the basis.
  3. Import back a short list of companies that external system flagged as unpaid, so VeriPrompt can act on them — downgrade to the free plan, suspend gateway access, or reactivate.

VeriPrompt also ships its own basic invoice/dunning tooling — draft/issue/void a manual invoice, credit notes, PDF/XRechnung/ZUGFeRD documents, automatic overdue reminders, a revenue dashboard, period close — for the case where you don't want to run a separate system for VeriPrompt subscriptions specifically. Using it is optional; the export/import workflow above is the primary, recommended path for anyone with a real accounting system already in place.

Access and permissions ​

Accounting access is carried by the ACCOUNTANT role, which comes in two shapes:

Company accountantPlatform accountant
DefinitionACCOUNTANT assigned to a companyACCOUNTANT with no company
SeesTheir own company onlyEvery company, or one at a time via a filter
Typical userA customer's own bookkeeperVeriprompt finance

A company accountant is pinned to their own company by the API regardless of any companyId they pass, so cross-company figures cannot leak to them.

ACCOUNT_OWNER, ADMIN and SUPER_ADMIN can also reach the billing dashboard. Package and price changes remain SUPER_ADMIN only.

A plain ACCOUNTANT (of either shape) is read-only: it can view and export everything below, but cannot create/issue/void an invoice, create a credit note, record a payment, close a period, or run the bulk unpaid-companies import. Those mutations require ACCOUNT_OWNER, ADMIN or SUPER_ADMIN — and the bulk import and period close are narrower still (see their own sections).

Every accountant read and export is written to the audit log (who looked at, or downloaded, what — see Audit logging below).

Your screens ​

ScreenPathWhat it is for
Billing dashboard/accountantBilling status, invoices, usage and provider mix — your daily view
Customers/accountant/customersCustomer master data (legal name, VAT ID, address...), CSV/XLSX export, bulk unpaid-action import
Contracts/accountant/contractsMinimum term, agreed price, notice period per company, CSV/XLSX export
Revenue/accountant/revenueMRR/ARR, deferred revenue, revenue by package/company
Approvals/accountant/approvalsPending four-eyes approvals for large invoice issue/void
Invoicing report/admin/invoicingPer-company billable figures for one month, exportable — your invoicing basis
Payment management/admin/payment-managementPayment status, grace periods, suspension
Billing & plan/billingThe self-serve view a customer sees of their own plan
Packages/admin/packagesThe plan catalogue and who is on which plan

The data you are working with ​

Three layers, and it matters which one you are looking at:

  1. Usage — every gateway call, with providerCost (what the provider charged us), markup and cost (what the customer is charged). This is the raw meter.
  2. Subscription — the company's package, billing period, contracted amount and the period-to-date usage counters.
  3. Invoice — a document with line items, subtotal, tax, total, amount paid and amount due — either a MANUAL invoice you drafted/issued yourself, or a STRIPE invoice created by a Stripe webhook. Both origins live in the same Invoice table and both appear in the invoice list and exports.

Usage is continuous, subscriptions are periodic, invoices are discrete. Most month-end mistakes come from comparing two of them across different period boundaries.

Task: review a company's billing position ​

  1. Go to /accountant.
  2. As a platform accountant, pick the company from the company filter; leave it on All Companies for the platform-wide view.
  3. Read, in order:
    • Payment status — CURRENT, GRACE_PERIOD, OVERDUE, SUSPENDED or EXEMPT, plus the grace-period end date.
    • Current package — name, monthly and yearly price, billing period, next billing date.
    • Usage against the plan — requests and tokens used versus the package allowance.
    • Provider mix — cost share per provider, which tells you why a bill moved.
    • Six-month trend and month-over-month growth — the sanity check before you invoice an unusual number.

Task: export data for your external accounting system ​

This is the primary workflow. Everything here is read-only and audit-logged.

Customer master data ​

GET /api/accountant/customers/export?format=csv|xlsx (also reachable from Export CSV / Export XLSX on /accountant/customers). One row per company: display name, legal name, account email, billing email, invoice address (billing address, falling back to the company's address), city, postal code, country, national tax ID, EU VAT ID plus its validation status, registration number, payment terms (days), currency, current package, payment status and whether the company is active. This is the field set most external invoicing systems need to be seeded with a customer.

Scoping is the same as every accountant route: a company-scoped accountant/admin gets only their own company; a platform accountant or SUPER_ADMIN gets every company, or one via ?companyId=.

Contracts ​

GET /api/accountant/contracts?format=csv|xlsx — the same list shown on /accountant/contracts, as a file: contract ID, company, package, start date, minimum term (months), notice period (days), auto-renew, agreed price/month, discount %, order reference, signed-at, ended-at, end reason.

Accounting-system postings ​

  • GET /api/accountant/exports/datev?period=YYYY-MM — DATEV EXTF Buchungsstapel for the period.
  • GET /api/accountant/exports/journal?period=YYYY-MM — a generic debit/credit journal-entry CSV, for any accounting system that isn't DATEV.
  • GET /api/accountant/receivables?format=csv — aged receivables (current / 0-30 / 31-60 / 61-90 / 90+ days), one row per company per bucket. (There is no invoice-count-per-bucket column — the underlying aggregation tracks summed amounts per bucket only, not a count of contributing invoices.)
  • GET /api/accountant/invoices/export?format=csv|xlsx — the invoice list, one row per invoice: invoice number, date, customer, VAT ID, net, tax rate, tax, gross, currency, FX rate, paid, open, due date.
  • GET /api/accountant/usage/export?period=current|previous|YYYY-MM — usage CSV, described in the next task.

Task: export usage for a period ​

From the dashboard: /accountant has a period selector (current month, previous month, or a specific past month) next to the Export button. Columns are: Company (cross-company exports only), Date, Time, Action, API Endpoint, Provider, Model, Total/Input/Output Tokens, Provider Cost, Markup, Total Cost — with a TOTALS summary row at the bottom.

From the API, for scripting a recurring pull:

bash
# One company, a specific month
curl -s -o usage-2026-07.csv \
  "https://app.veriprompt.tech/api/accountant/usage/export?period=2026-07&companyId=<companyId>" \
  -H "Cookie: <your session>"
bash
# Every company, previous month
curl -s -o usage-previous.csv \
  "https://app.veriprompt.tech/api/accountant/usage/export?period=previous" \
  -H "Cookie: <your session>"

period accepts current, previous or YYYY-MM. Omitting companyId as a platform accountant exports every company, with a Company column so the file can be pivoted.

Task: produce the invoicing basis for a month ​

  1. Go to /admin/invoicing.
  2. Choose the period (YYYY-MM) and, optionally, one company.
  3. Generate. For each company you get: package type, prompt count, token total, the display currency, the total in that currency, the exchange rate used, and a breakdown into usage and monthly fee.
  4. Export to CSV and use it as the basis for the invoices you raise — either in your external accounting system, or, if you use VeriPrompt's own invoice tooling, as the line items you enter on a draft invoice.

How currency is decided ​

Each row follows one rule, in this order:

  1. A stored invoice exists (manual or Stripe) → the row is shown in that invoice's own currency. What was invoiced is what is reported.
  2. Usage showback only → USD is converted into the company's lead currency (baseCurrency, USD or EUR) at the platform rate in effect for that period (see next section — rates are versioned, so a prior month is not silently recomputed at today's rate).
  3. The platform grand total is always USD, summing each row's USD equivalent. A non-USD invoice with no available rate is excluded from that total rather than being guessed at.

Task: report on companies that didn't pay (bulk unpaid-action import) ​

This is the loop back from your external invoicing system: it ran its own dunning/aging and produced a list of companies that did not pay. Import that list here to act on it in VeriPrompt.

Who can do this: a platform accountant (ACCOUNTANT with no assigned company) or SUPER_ADMIN — full stop, even for a dry run. A company-scoped accountant, or a company's own ACCOUNT_OWNER/ADMIN, cannot run this against their own company; suspending or downgrading a company is a platform decision, not something a company can do to itself.

Where: the Import Actions button on /accountant/customers, backed by POST /api/accountant/customers/actions/import. GET on the same endpoint downloads a CSV template.

CSV format — three columns:

Company ID,Action,Notes
cuid_example1,DOWNGRADE_TO_FREE,90+ days overdue per external invoicing system
cuid_example2,SUSPEND,Repeated non-payment
cuid_example3,REACTIVATE,Payment received via bank transfer

Action is one of:

ActionEffect
DOWNGRADE_TO_FREEMoves the company onto the platform's free/default package
SUSPENDBlocks all gateway/API access immediately; the company keeps its current package
REACTIVATEClears suspension/grace-period state and restores payment status to current

Notes is optional free text recorded on the resulting audit log entry.

Validate first. Tick Validate First (a dry run) before applying — it parses the CSV, looks up each company, and reports what would change (previous/new payment status, previous/new package) without writing anything. Run the real import once the dry run looks right.

Every row's outcome — success or the specific error (e.g. company not found) — comes back in the response, and every applied (non-dry-run) row writes an audit log entry (accountant.customer_status.<action>) recording who did it, when, and the before/after state.

Task: chase an overdue account (VeriPrompt's own tooling) ​

If you use VeriPrompt's own invoices rather than an external system:

  1. /admin/payment-management lists every company with its payment status, grace-period end and package.
  2. Filter to OVERDUE / GRACE_PERIOD and export the list.
  3. Payment status and grace period are changed per company from the same screen (SUPER_ADMIN).
  4. Automatic dunning reminders go out on overdue issued invoices at 7 days (FRIENDLY_REMINDER), 21 days (FIRST_REMINDER) and 45 days (FINAL_NOTICE) past the due date. /accountant/invoices/[id]/dunning shows where one invoice stands and lets you force an immediate re-check after recording a payment.

Grace period is the window between a failed or missed payment and suspension; the default is seven days and it is set per company.

Task: use VeriPrompt's own manual invoice lifecycle (optional) ​

If you choose to invoice a customer directly in VeriPrompt rather than in an external system — useful for a customer billed by bank transfer or purchase order who has no Stripe subscription:

  1. Draft — POST /api/accountant/invoices with companyId, currency, periodStart, periodEnd, optional dueDate/notes, and at least one line item (description, quantity, unitAmountCents). A draft has no invoice number yet — numbers are only allocated on issue, so a draft that is abandoned never burns a number out of the legal sequence.
  2. Issue — POST /api/accountant/invoices/[id]/issue. This re-checks the company's VAT ID against VIES if it has one, determines the applicable tax (including reverse-charge where it applies), allocates the next gapless sequence number, and freezes the invoice. If the company has a four-eyes threshold configured and this invoice's amount meets it, issuing instead creates a pending approval request (see below) and the invoice stays a draft until a second approver signs off.
  3. Void — POST /api/accountant/invoices/[id]/void. Only works on a draft or an unpaid open invoice; an invoice with any payment recorded against it must be corrected with a credit note instead, so a real cash movement is never silently erased.
  4. Credit notes — POST /api/accountant/credit-notes (draft, against an issued invoice) then POST /api/accountant/credit-notes/[id]/issue (allocates its own number, checks it does not exceed the invoice's remaining creditable amount).
  5. Record a payment — POST /api/accountant/invoices/[id]/payments (also lists an invoice's payment history).
  6. Documents — GET /api/accountant/invoices/[id]/pdf (PDF), GET /api/accountant/invoices/[id]/xrechnung?buyerReference=... (standalone XRechnung XML — the buyerReference "Leitweg-ID" a German B2G recipient requires must be supplied per request; there is no stored default yet), and GET /api/accountant/invoices/[id]/zugferd (a ZUGFeRD-style hybrid PDF with an embedded EN16931 CII XML attachment — structurally valid, not a validated PDF/A-3 conformance document).

Four-eyes approval ​

A company can set fourEyesThresholdCents so that issuing or voiding an invoice at or above that amount requires a second approver before it takes effect. Pending requests are listed on /accountant/approvals (GET /api/accountant/approvals); a plain ACCOUNTANT can view them, only ACCOUNT_OWNER/ADMIN/SUPER_ADMIN can approve or reject. When the control is off, or an amount is under the threshold, issue/void proceeds immediately as above.

Billing period close ​

/accountant/periods/[period] (via GET /api/accountant/periods/[period]?companyId=) shows a company's period status: a live aggregate while open, a frozen BillingPeriodSnapshot once closed, and — for a closed period — whether later usage has since drifted from what was frozen. Closing (POST .../close) and reopening (POST .../reopen) a period, and rolling a closed period's late-usage delta forward into the next period as a true-up (POST .../roll-forward), are restricted to a platform accountant or SUPER_ADMIN — narrower than the rest of this page's ACCOUNT_OWNER/ADMIN/SUPER_ADMIN mutation gate, because closing your own company's books is not something a company-scoped admin should be able to do to themselves.

Revenue dashboard ​

/accountant/revenue (GET /api/accountant/revenue) — MRR/ARR, revenue by package, revenue by company (platform view only), deferred revenue, and a new/active/churned movement summary.

Audit logging ​

Every accountant read (dashboard view, invoice list, usage query) and every export (CSV, XLSX, DATEV, journal, receivables) is recorded — who looked at or downloaded what, when, and for which company. This is the same mechanism behind the "who touched this customer's financial data" question a compliance review eventually asks; you do not need to build that trail yourself on top of the exports.

Deleting a company account ​

A company account is deleted from /admin/companies, by a SUPER_ADMIN only — there is no self-service deletion. Deletion is immediate and irreversible: it cascades to every related record (invoices, usage logs, contracts, everything) in one operation, with no soft-delete, no grace period, and no report generated of what was purged. If you need a company account permanently removed, contact your platform administrator — understand that this cannot be undone, and take whatever exports you need (see the tasks above) before asking for it.

Currencies ​

ConceptWhere it is setWhat it affects
Platform billing currencyFixed: USDWhat is actually charged and enforced
Company lead currencyCompany.baseCurrency (USD or EUR)Display and reporting only
Invoice currencyStored on the invoiceThat invoice, permanently
USD→EUR rate/admin/currency-configConversion of showback figures; versioned (see below)

The lead currency is a display convention. It never changes what a customer is charged.

Exchange rates are versioned: /admin/currency-config shows the currently effective rate and (?history=EUR) its full history, so re-generating a prior month's report uses the rate that was actually in effect for that period, not today's rate.

Current limitations ​

Be aware of these before you build a process on top of the platform:

LimitationPractical effectWork around it by
XRechnung export needs a buyer reference supplied per requestNo stored default "Leitweg-ID" on the company yetPass ?buyerReference= on each export, or ask engineering to add a stored field if you need this routinely
Aged receivables has no per-bucket invoice countOnly summed amounts per bucket, no "how many invoices" figureCross-reference the invoice list export if you need a count
Company deletion has no purge reportYou cannot get a generated record of exactly what was deleted for a retained-for-legal-reasons checkExport everything you need before requesting deletion (see above)
No period picker for the invoicing report beyond YYYY-MM selection on /admin/invoicingYou cannot generate a multi-month range in one callCall /api/admin/invoicing/generate once per month and combine the exports

API reference ​

EndpointReturns
GET /api/accountant/billingPayment status, package, limits and period usage. Add ?companyId= as a platform accountant; omit it for a summary of every company
GET /api/accountant/invoicesInvoices with line items. Supports ?status=, ?companyId=, ?limit=, ?offset=
POST /api/accountant/invoicesCreate a draft manual invoice
POST /api/accountant/invoices/[id]/issueIssue a draft invoice (VAT re-check, tax, gapless numbering)
POST /api/accountant/invoices/[id]/voidVoid a draft/unpaid-open invoice
GET/POST /api/accountant/invoices/[id]/paymentsPayment history / record a payment or refund
GET /api/accountant/invoices/[id]/pdf | .../xrechnung | .../zugferdInvoice document formats
GET /api/accountant/invoices/export?format=csv|xlsxInvoice list export
GET/POST /api/accountant/credit-notes, POST .../[id]/issueCredit notes
GET/PUT /api/accountant/customersCustomer master data (read all fields; write is ADMIN-tier)
GET /api/accountant/customers/export?format=csv|xlsxCustomer master-data export
GET /api/accountant/customers/actions/import (template) / POST (apply)Bulk unpaid-companies action import
POST /api/accountant/customers/[id]/validate-vatManual VIES re-check
GET/POST /api/accountant/contracts?format=csv|xlsxContracts, list or export
GET /api/accountant/revenueRevenue dashboard
GET /api/accountant/approvalsPending four-eyes approvals
GET /api/accountant/receivables?format=csvAged receivables
GET /api/accountant/exports/datev?period=YYYY-MMDATEV EXTF Buchungsstapel
GET /api/accountant/exports/journal?period=YYYY-MMGeneric journal-entry CSV
GET /api/accountant/periods/[period]?companyId=Period status (live or frozen snapshot)
POST /api/accountant/periods/[period]/close | .../reopen | .../roll-forwardPeriod close lifecycle (platform accountant / SUPER_ADMIN only)
GET /api/accountant/usageCurrent and previous month totals, provider mix, six-month trend, month-over-month growth
GET /api/accountant/usage/exportUsage CSV. Supports ?period=current|previous|YYYY-MM and ?companyId=
GET /api/admin/invoicing/generate?period=YYYY-MMBillable figures per company for the period, currency-resolved
GET /api/admin/payment-managementPayment status across companies (SUPER_ADMIN)
GET /api/admin/currency-config (?history=EUR)Current and historical USD→EUR rate
DELETE /api/admin/companies/[id]Permanently delete a company (SUPER_ADMIN only, immediate, irreversible)

Troubleshooting ​

SymptomCauseFix
403 on the bulk unpaid-action importYou are a company-scoped accountant/adminThis action is platform-accountant or SUPER_ADMIN only, by design — a company cannot suspend or downgrade itself
Import row fails with "Company not found"The Company ID column doesn't match a real company IDIDs differ per environment — export the customer list first and copy IDs from there, don't reuse IDs across staging/production
Issuing an invoice returns a pending-approval response instead of issuingThe company has a four-eyes threshold and this amount meets itA second approver must act on /accountant/approvals before the invoice issues
Voiding an invoice is rejectedThe invoice has a payment recorded against itIssue a credit note instead — void only works on a draft or an unpaid open invoice
403 on payment management as a platform accountantThat endpoint is SUPER_ADMIN only, although the link is offeredAsk a platform administrator for the export
A prior month's EUR total looks different from what you rememberCheck whether it changed because the rate genuinely changed for that period, or because you're comparing against today's rateUse ?history=EUR on /api/admin/currency-config to see the rate that was actually effective
Dashboard usage and invoicing report disagreeThey aggregate different logsReconcile before invoicing; treat the invoicing report as the billing basis
Usage counters look low mid-periodThey are per subscription period, and reset when the package is reassignedCheck the subscription start date on /admin/packages → Subscriptions
Company deletion can't be undoneBy design — no soft-delete existsTake every export you need before requesting deletion from a SUPER_ADMIN

Learn more ​