Monetization
Invoices
What Kaiten composes at each boundary of a subscription — kinds, lines and totals, statuses, holds, voids and recomposition, settling what you collect yourself, overdue invoices, and the usage behind a metered line — verified against the API.
Kaiten composes every invoice: at each boundary of a subscription, from the prices it bills and the usage journal. Kaiten is the reference for its lines and its amounts. Who makes it the legal invoice is whoever collects it: Stripe numbers it, sends it, collects it, and computes its tax when the connector's automatic tax is on — see Stripe; with no provider, your own accounting system issues it from the handoff queue. An invoice outlives its instance and its customer, and keeps them as they were when it was composed. Reading invoices needs read:billing, every action on them write:billing, and both need billing to be on.
The demo seed writes the catalog but bills nothing: the invoices below are the ones Kaiten Sushi Shop's subscriptions issue once it sells its Licenses through Kaiten.
Kinds and boundaries
kind | Composed | Bills |
|---|---|---|
ACTIVATION | When a subscription starts, or when its trial ends | The first period's base and add-on fees, in advance. A subscription whose base bills in arrears has none |
RENEWAL | At each boundary | The period that ended, in arrears — the metered usage, and the fees billed in arrears — and the period that starts, in advance |
FINAL | When a subscription ends, at its boundary or at once | What is left in arrears |
A period closes shortly after its boundary, or at once with POST /billing/close-periods; a subscription several periods behind closes them one boundary at a time. Within its subscription, an invoice is identified by its kind and its boundaryAt, and a renewal or a final invoice is issued even with no line, as the record of its boundary. Where a move to another price applies at a boundary, its renewal bills the old price's arrears and the new price's advance.
Lines and totals
Line type | Bills | quantity |
|---|---|---|
BASE | The subscription's flat fee | 1 |
ADDON | An add-on's flat fee, for the quantity held | The quantity held |
USAGE | A usage-based price, on the usage of the period | The usage, in sale units |
OVERAGE | An overage price, on the usage above the grant | The usage above the limit, in sale units |
DISCOUNT | A voucher's price discount, as a negative amount | 1 |
Each line carries its service period — serviceFrom inclusive, serviceTo exclusive — its label, a description that writes its arithmetic out (6 × 0.50 EUR; 106 used; 6 above the applied limit (100)), its quantity as a decimal string, the price's unitAmountDecimal, and its amount: the quantity times the unit amount, rounded half up once, in minor units. A metered line adds metering, how its quantity was measured, and an overage line overage, the limits that applied; a discount line adds discount, which invoice of its redemption it is out of how many, and the lines it bears on.
The invoice's subtotal is the sum of its lines, discountTotal the sum of its discounts, and total the difference — integers in minor units, never below 0: discounts stop at the subtotal. See money. Demo Restaurant's activation shows the arithmetic: its base line, Standard, monthly, at 4900; the discount of its WELCOME20 voucher, Welcome offer, at -980 — invoice 1 of the 3 the redemption discounts; a total of 3920, 39.20 EUR, charged to its card by Stripe.
Statuses
status | Meaning |
|---|---|
DRAFT | Composed, not issued: held, or on its way to its payment provider |
PUSHED | Issued by its payment provider, waiting for payment |
PUSH_FAILED | Its push to the provider failed; it is tried again, with a growing delay |
MANUAL | Issued, for you to collect: it has no provider. The console reads it Ready to bill |
PAYMENT_FAILED | The provider's automatic charge was refused, or waits for the customer to authenticate it |
PAID | Final: collected by its provider, marked paid, or issued paid because nothing was owed |
UNCOLLECTIBLE | Final: written off |
VOID | Final: voided, its boundary free to be composed again |
An invoice with nothing owed is issued PAID at once, and is neither pushed nor handed off: Ninja Osaka's free Starter subscription issues invoices of 0.00. Otherwise, with no provider, an invoice is issued MANUAL, due once its payment terms have run, and waits in the handoff queue (handoffStatus PENDING); with Stripe, it enters the push queue as a DRAFT, and Stripe issues it. What Stripe does from there — the finalization, the charge, the comparison of its amounts with Kaiten's — is on Stripe.
Holds
A period closes into a held DRAFT — neither issued, pushed, nor handed off — when what it would bill cannot be taken as is. holdReason says why, and INSTANCE_INVOICE_HELD is emitted:
holdReason | Why |
|---|---|
LEDGER_SEQUENCE_GAP, LEDGER_CHAIN_BREAK, LEDGER_COUNTER_MISMATCH | The usage journal of a metered entitlement failed a consistency check; holdDetail names every meter that failed, with what was expected and what was found |
PROVIDER_ITEM_LIMIT | The payment provider cannot take that many lines on one invoice — Stripe takes 250 |
Three ways lead out of a hold:
- Release —
POST /invoices/{id}/release-hold, with areasonof 1 to 500 characters, accepts the invoice as composed and issues it, keeping who released it and why. APROVIDER_ITEM_LIMIThold cannot be released, since its provider would refuse the invoice (409 ReleaseInvoiceHold.ProviderItemLimit): move the subscription to no provider, then recompose it. - Recompose —
POST /invoices/{id}/recomposerewrites the draft in place from the usage journal as it is now, keeping its base lines as they were sold, under the subscription's current provider and terms: issued when it is now sound, held again otherwise. - Void — below.
A later check releases a hold whose journal turns out sound, but an inconsistency in an append-only journal does not mend by itself: most holds wait for a person. Leaving a hold emits INSTANCE_INVOICE_RELEASED, and the invoice is issued in the same transaction. Before a boundary, the upcoming invoice of a subscription names the meters that would hold it (wouldHold).
Void and recompose
POST /invoices/{id}/void, with a reason of 1 to 500 characters, voids an invoice not yet settled: a draft, held or not, a MANUAL invoice, or one a provider issued. A provider's invoice is voided there first, and in Kaiten only once the provider confirms — 503 VoidInvoice.ProviderUnavailable leaves it as it was — and one the provider reports paid is refused. A paid or written-off invoice is never voided (409 VoidInvoice.InvalidStatus). A handoff still pending stays pending, so your accounting system sees the void. INSTANCE_INVOICE_VOIDED is emitted.
A void invoice frees its boundary. POST /invoices/{id}/recompose on it composes a replacement for that boundary — 201 Created — under the subscription's current terms and billing e-mail, its base lines as they were sold and its metered lines measured again; the two point to each other (replacesInvoiceId, replacedByInvoiceId), and a void invoice is replaced once (409 RecomposeInvoice.AlreadyReplaced). Any other invoice is voided before it is recomposed (409 RecomposeInvoice.InvalidStatus). A recompose is refused once the instance is deleted (409 RecomposeInvoice.InstanceDeleted), and when the period it bills starts before the usage history the organization keeps (422 RecomposeInvoice.OutsideRetention, which names retentionStart).
When the amounts Stripe holds differ from Kaiten's, INSTANCE_INVOICE_RECONCILIATION_MISMATCH is emitted, and the way out is the same: void, then recompose. Kaiten never corrects itself to match the provider.
Invoices you collect yourself
With no provider, an invoice with something owed is issued MANUAL and waits in the handoff queue, from which your accounting system claims it and acknowledges it — see No provider. Its payment happens outside Kaiten, so you record how it ends:
- Mark paid —
POST /invoices/{id}/mark-paid, with an optionalexternalReference— its number in your accounting system, 1 to 255 characters — apaidAt, now or earlier (422 MarkInvoicePaid.PaidAtInFuture), and anoteof up to 1,000 characters, kept only in the event. It acknowledges a handoff still pending in the same write. Marking it paid again with the same reference answers it unchanged; another reference is refused (409 MarkInvoicePaid.ReferenceMismatch). - Write off —
POST /invoices/{id}/write-off, with areasonof 1 to 500 characters: the invoice becomesUNCOLLECTIBLE. A handoff still pending stays pending, so your accounting system sees it.
Only a MANUAL invoice is settled this way (409 MarkInvoicePaid.InvalidStatus, WriteOffInvoice.InvalidStatus). An invoice a payment provider collects is settled there, and both calls refuse it (409 MarkInvoicePaid.ProviderManaged, WriteOffInvoice.ProviderManaged). INSTANCE_INVOICE_PAID — whose source says how it was paid — and INSTANCE_INVOICE_MARKED_UNCOLLECTIBLE are emitted.
Overdue invoices
overdue is computed each time an invoice is read or listed, and ?overdue=true filters the list on it:
- an invoice sent for payment (
SEND_INVOICE) is overdue once itsdueAt— its issue plus its payment terms — has passed unpaid; - an invoice charged automatically is overdue once its charge is refused — a charge waiting for the customer to authenticate it excepted — or once a grace period after its issue has passed unpaid: two hours by default on a self-hosted stack (
KAITEN_BILLING_AUTO_COLLECTION_GRACE).
It is not stored, so the answers of writes and the event payloads carry false. An overdue invoice moves its subscription to PAST_DUE, which changes no entitlement; once none of its invoices is overdue any more — paid, written off, or voided — the subscription is ACTIVE again.
The usage behind a metered line
A USAGE or OVERAGE line says what it was measured from. Its metering member holds the line's fingerprint in the usage journal — the first and last report of the period (firstSeq, lastSeq), how many there were (rows), and the sums of their movements (sumDelta, sumOverage) — with the quantity it measured, the reset windows the period spans, and how many windows moved down and were counted as 0. An OVERAGE line adds overage: the usage, the part of it above the limit, and the limits that applied, in order.
GET /invoices/{id}/lines/{lineId}/reports lists the reports themselves: the accepted usage reports of the line's instance and entitlement dated in its service period, in report order — each with when it was accepted, how it moved the counter (valueBefore, valueAfter, delta), its overageDelta, the limit and allowance in force, and its transactionId, the Idempotency-Key it was reported with. Summed per reset window, each window floored at 0, they give the line's measured quantity. The list pages with afterSeq — 100 reports by default, 500 at most — and ?format=csv streams every report as CSV instead. It still answers once the instance is deleted, and refuses a period older than the usage history the organization keeps (422 ListInvoiceLineReports.OutsideRetention); the line's fingerprint is then what remains.
kappa-kyoto-prod reports its orders in batches from its point of sale, each with an Idempotency-Key naming the batch — pos-kyoto-20260908-0930 — which its invoices' lines lead back to. In the console, a metered line of an invoice opens its reports, grouped by reset window, with Export CSV:
List and export
GET /invoices lists the organization's invoices, newest first, and filters them by status (several at once), kind, providerKind, customerSlug, instanceSlug, overdue, held, handoffStatus, the day they were issued (issuedFrom, issuedTo), and their boundary (boundaryFrom, boundaryTo). With updatedSince it lists the invoices changed since an instant, oldest change first, for an incremental sync. A customer or instance filter matches the slug it has now as well as the one an invoice was composed under. GET /instances/{slug}/invoices lists one instance's invoices, across every time it was subscribed.
GET /invoices/export streams what the same filters select: CSV by default — one row per line, or per invoice with granularity=invoice — or NDJSON with format=json, one invoice and its lines per line. Amounts are integers in minor units with the currency's exponent beside them, so a spreadsheet never reads a float. The export carries billing e-mails: it is meant for your accounting.
In the console, Invoices lists every invoice of the organization in views — All, Overdue, Held, and, where invoices are handed off to your own system, Handoff and Acknowledged — with a search, filters, and the export:
API endpoints
| Method | Endpoint | Operation | Description |
|---|---|---|---|
GET | /invoices | listInvoices | The organization's invoices, filtered (cursor-paginated) |
GET | /instances/{slug}/invoices | listInstanceInvoices | One instance's invoices, with the same filters |
GET | /invoices/{invoiceId} | getInvoice | One invoice with its lines, its hold, and its handoff |
GET | /invoices/export | exportInvoices | Stream the selected invoices as CSV or NDJSON |
GET | /invoices/{invoiceId}/lines/{lineId}/reports | listInvoiceLineReports | The usage reports behind a metered line; ?format=csv for all of them |
POST | /invoices/{invoiceId}/mark-paid | markInvoicePaid | Record that a MANUAL invoice was paid |
POST | /invoices/{invoiceId}/write-off | writeOffInvoice | Write a MANUAL invoice off |
POST | /invoices/{invoiceId}/void | voidInvoice | Void an invoice not yet settled — at its provider first |
POST | /invoices/{invoiceId}/recompose | recomposeInvoice | Rewrite a held draft in place, or replace a void invoice |
POST | /invoices/{invoiceId}/release-hold | releaseInvoiceHold | Accept a held invoice as composed and issue it |
POST | /invoices/{invoiceId}/retry-push | retryInvoicePush | Put a draft or a failed push back in its provider's push queue now |
POST | /invoices/{invoiceId}/sync | syncInvoice | Read an invoice from its provider now, and apply what it says |
POST | /billing/close-periods | closeBillingPeriods | Close the periods that have ended, without waiting for the next pass |
Reads need read:billing, actions write:billing. The handoff queue's own operations are on No provider.
Events
| Event name | Event type | When |
|---|---|---|
INSTANCE_INVOICE_ISSUED | com.kaiten.instance.invoice.v1.issued | An invoice with something owed is issued — once per invoice |
INSTANCE_INVOICE_PUSHED | com.kaiten.instance.invoice.v1.pushed | Its payment provider has issued it |
INSTANCE_INVOICE_PUSH_FAILED | com.kaiten.instance.invoice.v1.push_failed | Pushing it has failed as many times as the alert threshold, then once a day while it keeps failing |
INSTANCE_INVOICE_PAID | com.kaiten.instance.invoice.v1.paid | It is paid — collected by its provider, marked paid, or issued paid because nothing was owed; source says which |
INSTANCE_INVOICE_PAYMENT_FAILED | com.kaiten.instance.invoice.v1.payment_failed | Its automatic charge is refused, or needs the customer to authenticate it |
INSTANCE_INVOICE_HELD | com.kaiten.instance.invoice.v1.held | A period closes into a held draft |
INSTANCE_INVOICE_RELEASED | com.kaiten.instance.invoice.v1.released | A held invoice leaves its hold and is issued |
INSTANCE_INVOICE_VOIDED | com.kaiten.instance.invoice.v1.voided | It is voided |
INSTANCE_INVOICE_MARKED_UNCOLLECTIBLE | com.kaiten.instance.invoice.v1.marked_uncollectible | It is written off |
INSTANCE_INVOICE_HANDOFF_ACKNOWLEDGED | com.kaiten.instance.invoice.v1.handoff_acknowledged | Your accounting system acknowledges it from the handoff queue, or it is marked paid while still waiting there |
INSTANCE_INVOICE_RECONCILIATION_MISMATCH | com.kaiten.instance.invoice.v1.reconciliation_mismatch | Its payment provider holds amounts that differ from Kaiten's |
Each one names its instance in its payload and is recorded on the organization's audit trail. The event catalog has every payload.
Where to go next
Who collects an invoice: Stripe, or your own system through no provider. What each boundary bills, from the subscription's side: Subscriptions. The usage journal the metered lines are measured from: Usage & Metering. Exact reference: the billing tables of the data model.
Subscriptions
How Kaiten bills one Instance — the flat fee it is pinned to, its periods and boundaries, its statuses from trial to cancellation, payment terms, moves to another price, the fallback License, and what a live subscription freezes — verified against the API.
Add-ons
Extensions sold on top of a License and held by an Instance in a quantity — families and versions, what one unit grants and how it composes with the License, prices and compatibility, and how a change of quantity is billed.