Kaiten

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

kindComposedBills
ACTIVATIONWhen a subscription starts, or when its trial endsThe first period's base and add-on fees, in advance. A subscription whose base bills in arrears has none
RENEWALAt each boundaryThe period that ended, in arrears — the metered usage, and the fees billed in arrears — and the period that starts, in advance
FINALWhen a subscription ends, at its boundary or at onceWhat 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 typeBillsquantity
BASEThe subscription's flat fee1
ADDONAn add-on's flat fee, for the quantity heldThe quantity held
USAGEA usage-based price, on the usage of the periodThe usage, in sale units
OVERAGEAn overage price, on the usage above the grantThe usage above the limit, in sale units
DISCOUNTA voucher's price discount, as a negative amount1

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.

Demo Restaurant's activation invoice, paid through Stripe: a total of €39.20 for 2 lines after €9.80 of discounts, paid this morning, for the period of a month; its summary with 30-day payment terms; who it was billed to, as composed; the Stripe card with its status, invoice number, and identifiers, amounts matching, and links to the hosted invoice and the PDF; the reconciliation, a match; and the lines — Standard, monthly at €49.00 and the Welcome offer discount at −€9.80, 20 % off the base price, invoice 1 of 3.Demo Restaurant's activation invoice, paid through Stripe: a total of €39.20 for 2 lines after €9.80 of discounts, paid this morning, for the period of a month; its summary with 30-day payment terms; who it was billed to, as composed; the Stripe card with its status, invoice number, and identifiers, amounts matching, and links to the hosted invoice and the PDF; the reconciliation, a match; and the lines — Standard, monthly at €49.00 and the Welcome offer discount at −€9.80, 20 % off the base price, invoice 1 of 3.

Statuses

statusMeaning
DRAFTComposed, not issued: held, or on its way to its payment provider
PUSHEDIssued by its payment provider, waiting for payment
PUSH_FAILEDIts push to the provider failed; it is tried again, with a growing delay
MANUALIssued, for you to collect: it has no provider. The console reads it Ready to bill
PAYMENT_FAILEDThe provider's automatic charge was refused, or waits for the customer to authenticate it
PAIDFinal: collected by its provider, marked paid, or issued paid because nothing was owed
UNCOLLECTIBLEFinal: written off
VOIDFinal: 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:

holdReasonWhy
LEDGER_SEQUENCE_GAP, LEDGER_CHAIN_BREAK, LEDGER_COUNTER_MISMATCHThe 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_LIMITThe 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 a reason of 1 to 500 characters, accepts the invoice as composed and issues it, keeping who released it and why. A PROVIDER_ITEM_LIMIT hold 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}/recompose rewrites 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 optional externalReference — its number in your accounting system, 1 to 255 characters — a paidAt, now or earlier (422 MarkInvoicePaid.PaidAtInFuture), and a note of 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 a reason of 1 to 500 characters: the invoice becomes UNCOLLECTIBLE. 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 its dueAt — 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 Invoices page on its Overdue view: one invoice, Kappa Kyoto's renewal of €244.65, due yesterday, with no payment provider, its handoff acknowledged by the accounting system.The Invoices page on its Overdue view: one invoice, Kappa Kyoto's renewal of €244.65, due yesterday, with no payment provider, its handoff acknowledged by the accounting system.

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:

The reports behind the Orders above the allowance line of a Kappa Kyoto renewal: 84 Monthly Orders above the 1,000 Premium v1 grants, within its 20 % allowance, billed €12.60; reports 101 to 107, seven rows summing 699; then the June window's five reports, each with its counter before and after, its change, its overage change, the limit of 1,000, and its batch's transaction key; and Export CSV.The reports behind the Orders above the allowance line of a Kappa Kyoto renewal: 84 Monthly Orders above the 1,000 Premium v1 grants, within its 20 % allowance, billed €12.60; reports 101 to 107, seven rows summing 699; then the June window's five reports, each with its counter before and after, its change, its overage change, the limit of 1,000, and its batch's transaction key; and 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:

The Invoices page, All view, with the counts All 10, Overdue 1, Held 0, Handoff 1, Acknowledged 4: Kappa Kyoto's renewals with no payment provider — this morning's ready to bill and waiting for the accounting system, August's overdue, the earlier ones paid and acknowledged; Demo Restaurant's activation of €39.20 after €9.80 of discounts and Sakura Tokyo's of €3,280.00, both paid through Stripe; and Ninja Osaka's invoices of €0.00, paid.The Invoices page, All view, with the counts All 10, Overdue 1, Held 0, Handoff 1, Acknowledged 4: Kappa Kyoto's renewals with no payment provider — this morning's ready to bill and waiting for the accounting system, August's overdue, the earlier ones paid and acknowledged; Demo Restaurant's activation of €39.20 after €9.80 of discounts and Sakura Tokyo's of €3,280.00, both paid through Stripe; and Ninja Osaka's invoices of €0.00, paid.

API endpoints

MethodEndpointOperationDescription
GET/invoiceslistInvoicesThe organization's invoices, filtered (cursor-paginated)
GET/instances/{slug}/invoiceslistInstanceInvoicesOne instance's invoices, with the same filters
GET/invoices/{invoiceId}getInvoiceOne invoice with its lines, its hold, and its handoff
GET/invoices/exportexportInvoicesStream the selected invoices as CSV or NDJSON
GET/invoices/{invoiceId}/lines/{lineId}/reportslistInvoiceLineReportsThe usage reports behind a metered line; ?format=csv for all of them
POST/invoices/{invoiceId}/mark-paidmarkInvoicePaidRecord that a MANUAL invoice was paid
POST/invoices/{invoiceId}/write-offwriteOffInvoiceWrite a MANUAL invoice off
POST/invoices/{invoiceId}/voidvoidInvoiceVoid an invoice not yet settled — at its provider first
POST/invoices/{invoiceId}/recomposerecomposeInvoiceRewrite a held draft in place, or replace a void invoice
POST/invoices/{invoiceId}/release-holdreleaseInvoiceHoldAccept a held invoice as composed and issue it
POST/invoices/{invoiceId}/retry-pushretryInvoicePushPut a draft or a failed push back in its provider's push queue now
POST/invoices/{invoiceId}/syncsyncInvoiceRead an invoice from its provider now, and apply what it says
POST/billing/close-periodscloseBillingPeriodsClose 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 nameEvent typeWhen
INSTANCE_INVOICE_ISSUEDcom.kaiten.instance.invoice.v1.issuedAn invoice with something owed is issued — once per invoice
INSTANCE_INVOICE_PUSHEDcom.kaiten.instance.invoice.v1.pushedIts payment provider has issued it
INSTANCE_INVOICE_PUSH_FAILEDcom.kaiten.instance.invoice.v1.push_failedPushing it has failed as many times as the alert threshold, then once a day while it keeps failing
INSTANCE_INVOICE_PAIDcom.kaiten.instance.invoice.v1.paidIt is paid — collected by its provider, marked paid, or issued paid because nothing was owed; source says which
INSTANCE_INVOICE_PAYMENT_FAILEDcom.kaiten.instance.invoice.v1.payment_failedIts automatic charge is refused, or needs the customer to authenticate it
INSTANCE_INVOICE_HELDcom.kaiten.instance.invoice.v1.heldA period closes into a held draft
INSTANCE_INVOICE_RELEASEDcom.kaiten.instance.invoice.v1.releasedA held invoice leaves its hold and is issued
INSTANCE_INVOICE_VOIDEDcom.kaiten.instance.invoice.v1.voidedIt is voided
INSTANCE_INVOICE_MARKED_UNCOLLECTIBLEcom.kaiten.instance.invoice.v1.marked_uncollectibleIt is written off
INSTANCE_INVOICE_HANDOFF_ACKNOWLEDGEDcom.kaiten.instance.invoice.v1.handoff_acknowledgedYour accounting system acknowledges it from the handoff queue, or it is marked paid while still waiting there
INSTANCE_INVOICE_RECONCILIATION_MISMATCHcom.kaiten.instance.invoice.v1.reconciliation_mismatchIts 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.

On this page