Monetization
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.
A subscription is the billing of one Instance, kept by Kaiten — never by the payment provider. An Instance has at most one: a canceled subscription is subscribed again on the same record. It is pinned to one flat fee of the Instance's License version, its base price, which sets its billing period and its currency; the version's active metered prices bill with it. Its periods are counted from its anchor, and each one ends at a boundary, where the period closes into an invoice. Reading a subscription needs read:billing, every change write:billing, and both need billing to be on.
Examples use $KAITEN_API and $KAITEN_TOKEN as set up in the quickstart. The demo seed writes the catalog but sells nothing: the subscriptions below are the ones Kaiten Sushi Shop holds once it sells its Licenses through Kaiten, on the seeded prices. Responses are illustrative — ids and timestamps depend on your stack.
Statuses
| Status | What it means | Live |
|---|---|---|
TRIAL | A trial: nothing is billed, and the usage of the trial is never billed. The first invoice is issued when it ends | Yes |
INCOMPLETE | A checkout with an amount due waits for its first payment. Nothing it bought applies before that payment, and an unpaid one ends after 23 hours | No |
ACTIVE | Billed every period | Yes |
PAST_DUE | One of its invoices is overdue. Nothing the Instance may use changes | Yes |
CANCELED | Ended: no further invoice. It can be subscribed again | No |
A live subscription — TRIAL, ACTIVE, or PAST_DUE — is one that bills: it freezes its Instance and the License version it bills. INCOMPLETE is reached only through a self-serve checkout; the operations on this page never enter it.
In the console, the Billing tab of an instance shows its subscription — status, provider, how it is collected and its terms, the base price, the current period and the next boundary — next to the upcoming invoice, the add-ons it holds, its voucher redemptions, and its invoices across every time it was subscribed:
Subscribe an instance
POST /instances/{slug}/billing starts billing an instance on an active flat fee of its License version, which must be published. Kaiten Sushi Shop subscribed Demo Restaurant to Standard v1, monthly, charged to its card through Stripe, with the WELCOME20 voucher:
PRICE_ID=$(curl -s "$KAITEN_API/licenses/standard/prices?billingModel=FLAT_FEE" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
| jq -r '.[] | select(.isDefault and .billingPeriod == "MONTHLY") | .id')
curl -X POST "$KAITEN_API/instances/demo-restaurant-prod/billing" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"basePriceId": "'"$PRICE_ID"'",
"providerKind": "STRIPE",
"collectionMethod": "CHARGE_AUTOMATICALLY",
"voucherCode": "WELCOME20"
}'{
"id": "…",
"instanceSlug": "demo-restaurant-prod",
"customerSlug": "demo-restaurant",
"status": "ACTIVE",
"providerKind": "STRIPE",
"collectionMethod": "CHARGE_AUTOMATICALLY",
"daysUntilDue": 30,
"billingPeriod": "MONTHLY",
"currency": "EUR",
"anchorAt": "2026-09-08T08:30:00Z",
"currentPeriodStart": "2026-09-08T08:30:00Z",
"currentPeriodEnd": "2026-10-08T08:30:00Z",
"trialEndsAt": null,
"activationInvoice": {
"id": "…",
"kind": "ACTIVATION",
"status": "PAID",
"currency": "EUR",
"subtotal": 4900,
"discountTotal": 980,
"total": 3920
}
}HTTP/1.1 201 Created, trimmed here to the members discussed, and INSTANCE_BILLING_STARTED is emitted. The base bills in advance, so the first period's invoice — the ACTIVATION — is composed in the same transaction: 49.00 EUR, less the voucher's 20 %, 39.20 EUR. Charged automatically, it is pushed to Stripe and charged before the answer when Stripe answers within 20 seconds; its status says how that went, here PAID. The call needs Stripe connected, a payment method on the customer, and the voucher to exist — see Stripe and Vouchers.
The body takes, beside basePriceId:
| Member | What it sets | Default |
|---|---|---|
providerKind | Who collects the invoices: STRIPE, or NOOP — no provider, your own system through the handoff queue | NOOP |
collectionMethod | SEND_INVOICE — the invoice is sent, payable within the terms — or CHARGE_AUTOMATICALLY — the provider charges the payment method on file, which only Stripe does | The organization's default, read at each invoice |
daysUntilDue | The payment terms, 0 to 365 days between an invoice's issue and its due date | The organization's default, read at each invoice |
startAt | The anchor, truncated to the second: now, or at most one billing period back — a contract that began earlier | Now |
trialDays | A trial of 1 to 365 days, or 0 for none — see Trials | The version's trialPeriodDays |
addOns | Add-ons to attach with it, with their quantities; one refused refuses the whole subscribe | None |
voucherCode | A voucher to redeem with it; a price voucher applies from the activation invoice | None |
What the subscribe refuses:
| Status | Code | When |
|---|---|---|
409 | SubscribeInstance.AlreadySubscribed | The instance already has a live subscription |
422 | SubscribeInstance.LicenseNotPublished | The instance's version is a draft or archived — only a version on sale is subscribed to |
404 / 422 | SubscribeInstance.PriceNotFound, .PriceNotOnInstanceLicense, .PriceNotFlatFee, .PriceDeprecated | The base is not an active flat fee of the instance's version |
422 | SubscribeInstance.StartAtInFuture, .StartAtTooEarly, .StartAtBeforeCancellation | The start is in the future, more than one billing period back, or before the end of the instance's previous subscription |
422 | SubscribeInstance.InvalidTrialDays, .InvalidDaysUntilDue | Out of 0 to 365 |
422 | SubscribeInstance.ProviderNotConnected, .CollectionMethodUnsupported, .BillingEmailMissing, .PaymentMethodRequired, .UnsupportedCurrency | The provider is not connected, cannot charge, needs the customer's billing e-mail to send the invoice or a payment method to charge, or refuses the currency |
422 | SubscribeInstance.AddonInvalid, .VoucherInvalid | An add-on or the voucher cannot be applied; errors[0].value.code carries the reason |
In the console, Subscribe on the Billing tab of an instance asks for the base price — the active flat fees of its version, the default first — the trial, the payment terms, the start in UTC, the add-ons, and a voucher code. It subscribes with no payment provider: its invoices go to the handoff queue, and Provider and terms moves the subscription to Stripe afterwards, from its next invoice.
Periods and boundaries
The first period starts at the anchor and lasts the base price's billing period — one, three, six, or twelve months, the day clamped to the end of a shorter month. currentPeriodStart and currentPeriodEnd bound the period under way; currentPeriodEnd is the next boundary, the instant the period closes and its invoice is composed. Usage reported before the subscription started is never billed. A subscription started one period back whose first period has already ended closes it on the next pass.
While a period that ended is being closed, every change to the subscription is refused with 409 <Operation>.BoundaryPending and Retry-After: 60, and nothing is written: wait, and send the same request again.
Trials
A trial lasts 1 to 365 days from the anchor. During it the subscription is TRIAL and its period is the trial: nothing is billed, and the usage of the trial is never billed. Where the subscribe names no trialDays, the subscription takes the trial of its License version, if it has one.
- One trial per customer and License family. A customer who already had a trial of the family — even one canceled at once — gets no default trial again; only an explicit
trialDaysgrants another. - A reminder before the end.
INSTANCE_BILLING_TRIAL_WILL_ENDis emitted once, the family'strialReminderDaysbefore the trial ends — three by default, as the family's setting stood when the trial started — or at once for a shorter trial.reminderAton the subscription says when. The event carries what a reminder e-mail needs: the customer and its billing e-mail, the instance, the License, the days left, the amount the conversion would charge, and the card it would charge. - The end converts it. At
trialEndsAtthe subscription becomesACTIVE, anchored at the trial's end, and its first invoice — theACTIVATION— is issued. - Without a payment method, the family decides. A trial charged automatically, for a total above 0, whose customer has no active payment method at its end, follows its family's
trialEndWithoutPaymentMethod:CANCEL, the default, cancels it at the trial's end with no invoice —cancellationReasonTRIAL_ENDED_WITHOUT_PAYMENT_METHOD— and the instance falls back;CONVERTmakes itACTIVE, and its first charge then fails.
A trial cannot move to another price (409 SchedulePlanChange.TrialInProgress): cancel it and subscribe again with the new one. Canceled, a trial ends at once, with no invoice.
The capture shows Kaiten Sushi Shop's delivery pilot: a 14-day trial of Standard v2 for ninja-osaka-delivery, with a card on file. It converts in three days, and its reminder went out this morning.
Payment terms and provider
How an invoice is collected is the subscription's own, or the organization's default where it has none: the collection method — SEND_INVOICE or CHARGE_AUTOMATICALLY — and the payment terms, the days until an invoice is due. The defaults are the organization's billing settings — SEND_INVOICE and 30 days until changed (GET /billing/settings, PUT /billing/settings) — and a subscription without its own terms reads them at each invoice. With no provider, an invoice is always sent: nothing charges it.
PATCH /instances/{slug}/billing changes a live subscription's provider, collection method, and payment terms from its next invoice on; a collection method or payment terms sent as null restore the organization's default. The invoices already composed keep their own terms and stay with the provider that issued them. A move to Stripe registers the customer with Stripe first, and 503 UpdateInstanceBilling.ProviderUnavailable leaves everything as it was when Stripe cannot be reached. A change of provider emits INSTANCE_BILLING_PROVIDER_CHANGED. In the console, it is Provider and terms on the Billing tab — Payment terms where Stripe is not offered.
Past due
An ACTIVE subscription with an overdue invoice becomes PAST_DUE, with pastDueSince; it is ACTIVE again once none of its invoices is overdue, and each move emits INSTANCE_BILLING_STATUS_CHANGED — reason INVOICE_OVERDUE, then SETTLED. An invoice sent for payment is overdue once its due date has passed unpaid; one charged automatically, once its charge is refused 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). Invoices has the details.
PAST_DUE changes no entitlement: the Instance keeps what its License, its add-ons, and its exceptions grant. What follows — a reminder, a payment recorded by hand, a cancellation — is your process.
Kappa Kyoto runs the archived Premium v1 at 240.00 EUR a month, with no payment provider: its invoices go to Kaiten Sushi Shop's accounting system through the handoff queue. Its August invoice — 244.65 EUR, the base and 31 orders above the allowance at 0.15 EUR — is overdue, so the subscription is PAST_DUE, and the instance keeps every entitlement of Premium v1.
Move to another price
PUT /instances/{slug}/billing/scheduled-change schedules a move to another flat fee — of any published License version, the same family or another, in the subscription's currency — for the next boundary, never before. That boundary's renewal bills the old price's arrears and the new price's advance, and moves the instance to the new price's version; nothing is prorated. From there, the periods are those of the new price, counted from the boundary. Scheduling another price replaces the change, scheduling the same one changes nothing, and DELETE on the same path drops it. Move Demo Restaurant to the yearly fee of Standard v1 at its next boundary:
YEARLY_PRICE_ID=$(curl -s "$KAITEN_API/licenses/standard/prices?billingModel=FLAT_FEE" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
| jq -r '.[] | select(.isDefault and .billingPeriod == "ANNUAL") | .id')
curl -X PUT "$KAITEN_API/instances/demo-restaurant-prod/billing/scheduled-change" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"licensePriceId": "'"$YEARLY_PRICE_ID"'"}'200 OK with the subscription, whose scheduledChange names the price, its version, and effectiveAt, the boundary; INSTANCE_BILLING_PLAN_CHANGE_SCHEDULED is emitted. The move is refused during a trial (409 SchedulePlanChange.TrialInProgress), while a cancellation is scheduled (409 .CancellationScheduled), on the subscription's own price (422 .SamePrice), in another currency (422 .CurrencyMismatch), on a price that is not an active flat fee of a published version (.PriceNotFlatFee, .PriceDeprecated, .LicenseNotPublished), and when an add-on the instance holds does not fit the new version's family (422 .AddonIncompatible) or an entitlement would be metered twice (422 .MeteredEntitlementConflict). While the change is scheduled, its price cannot be deprecated nor its version archived, and that version is frozen as a billed one. In the console, the Billing tab offers the same move, among the flat fees of the versions on sale, and says when it applies.
Cancel and reactivate
POST /instances/{slug}/billing/cancel ends a subscription — billing only. Its mode says when:
mode | What happens |
|---|---|
AT_PERIOD_END, the default | The period paid for runs out. cancelAtPeriodEnd is set and INSTANCE_BILLING_CANCELLATION_SCHEDULED emitted; at the boundary a FINAL invoice bills what is left in arrears — the usage of the period, and a fee billed in arrears — which may be nothing, and the subscription is CANCELED. Asking again changes nothing |
IMMEDIATE | The FINAL invoice is issued now, a fee billed in arrears in full, and nothing already billed is refunded. It cannot be undone |
A trial is canceled at once, with no invoice, whatever the mode. Either way a move to another price that was scheduled is dropped (INSTANCE_BILLING_PLAN_CHANGE_CANCELED), and INSTANCE_BILLING_CANCELED carries the mode and the final invoice. A reason of up to 500 characters is kept as cancellationReason (422 CancelSubscription.InvalidReason beyond); a subscription already canceled answers 409 CancelSubscription.NotActive.
Until the boundary, POST /instances/{slug}/billing/reactivate reverts a cancellation scheduled for the period's end and emits INSTANCE_BILLING_CANCELLATION_REVERTED. A subscription already canceled is subscribed again instead (409 ReactivateSubscription.Canceled).
Apart from the fallback, a cancellation changes billing only: the add-ons the instance holds, its voucher redemptions, and its license dates stay as they are, and the invoices already issued stay collectible. The console's cancel dialog offers two follow-ups beside the cancellation, each a request of its own: remove the add-ons, and set the end date of the License. A customer can cancel at the period's end, and reactivate, from your product through a customer session.
The fallback License
When a subscription ends — at the end of its period, at once, during its trial, or at the end of a trial without a payment method — and the family of the instance's License names a fallback, the instance moves to the fallback family's default published version, in the same transaction. INSTANCE_UPDATED is emitted with licenseChangeReason: BILLING_FALLBACK; only the instance's License changes, and the add-ons the fallback's family does not accept are removed at the cancellation's instant. Kaiten Sushi Shop's Standard and Premium fall back to Starter: when Demo Restaurant's subscription ends, demo-restaurant-prod runs Starter.
Where the family names no fallback — or where the fallback no longer qualifies, a version of it no longer FREE or no default published — the instance keeps its License, unbilled. A checkout that ends unpaid moves nothing: what it bought never applied.
What a live subscription freezes
- The instance's customer and License. A
PUT /instances/{slug}that changescustomerIdorlicenseIdis refused with409 UpdateInstance.BillingActive. The License changes through a move to another price. - What the version sells. Its grants, and new prices on it, are refused with
409 …BillingActive— see License Types. - Deletion. An instance is kept while its subscription lives or an invoice of it is not settled:
409 DeleteInstance.BillingActive, whoseerrors[0].valuenames the status and the unsettled invoices. A customer is kept the same way (409 DeleteCustomer.BillingActive). Once the subscription is canceled and its invoices settled, the instance can be deleted; its subscription and invoices stay readable.
Read it
GET /instances/{slug}/billing returns the subscription, live or canceled, with its effective terms and whether they are its own (collectionMethodOverride, daysUntilDueOverride); an instance never subscribed answers 404 GetInstanceBilling.NotFound. GET /instances/{slug}/billing/upcoming-invoice composes the invoice the next boundary will issue from the usage so far, and writes nothing; its wouldHold names any meter whose usage journal would hold it.
GET /customers/{slug}/billing returns a customer's billing e-mail — where its invoices are sent — and, for each payment provider, its identifier there and the labels of its payment method: brand, last four digits, expiry, never the card number. How a payment method is added, replaced, or removed is on Stripe.
API endpoints
| Method | Endpoint | Operation | Description |
|---|---|---|---|
GET | /instances/{slug}/billing | getInstanceBilling | The instance's subscription, live or canceled |
POST | /instances/{slug}/billing | subscribeInstance | Subscribe an instance, or subscribe a canceled one again |
PATCH | /instances/{slug}/billing | updateInstanceBilling | Change the provider, collection method, and payment terms, from the next invoice |
PUT | /instances/{slug}/billing/scheduled-change | schedulePlanChange | Schedule a move to another price for the next boundary |
DELETE | /instances/{slug}/billing/scheduled-change | cancelPlanChange | Drop the scheduled move |
POST | /instances/{slug}/billing/cancel | cancelSubscription | Cancel, at the period's end or at once |
POST | /instances/{slug}/billing/reactivate | reactivateSubscription | Revert a cancellation scheduled for the period's end |
GET | /instances/{slug}/billing/upcoming-invoice | getUpcomingInvoice | The invoice the next boundary will issue, from the usage so far |
GET | /customers/{slug}/billing | getCustomerBilling | A customer's billing e-mail and payment method labels |
GET | /billing/settings | getBillingSettings | The organization's default collection method and payment terms |
PUT | /billing/settings | updateBillingSettings | Replace them; subscriptions without terms of their own take them at their next invoice |
Reads need read:billing, changes write:billing.
Events
| Event name | Event type | When |
|---|---|---|
INSTANCE_BILLING_STARTED | com.kaiten.instance.billing.v1.started | An instance is subscribed, or subscribed again (resubscribed) |
INSTANCE_BILLING_STATUS_CHANGED | com.kaiten.instance.billing.v1.status_changed | A trial converts, a checkout is paid, or a subscription enters or leaves PAST_DUE; from, to, and reason say how |
INSTANCE_BILLING_TRIAL_WILL_END | com.kaiten.instance.billing.v1.trial_will_end | Once per trial, at reminderAt |
INSTANCE_BILLING_CANCELLATION_SCHEDULED | com.kaiten.instance.billing.v1.cancellation_scheduled | A subscription is set to cancel at the end of its period |
INSTANCE_BILLING_CANCELLATION_REVERTED | com.kaiten.instance.billing.v1.cancellation_reverted | That cancellation is reverted |
INSTANCE_BILLING_CANCELED | com.kaiten.instance.billing.v1.canceled | A subscription is canceled — at its period's end, at once, or during or at the end of its trial |
INSTANCE_BILLING_PLAN_CHANGE_SCHEDULED | com.kaiten.instance.billing.v1.plan_change_scheduled | A move to another price is scheduled, or replaced |
INSTANCE_BILLING_PLAN_CHANGE_CANCELED | com.kaiten.instance.billing.v1.plan_change_canceled | A scheduled move is dropped — by request, or by a cancellation |
INSTANCE_BILLING_PLAN_CHANGED | com.kaiten.instance.billing.v1.plan_changed | A scheduled move is applied at a boundary |
INSTANCE_BILLING_PROVIDER_CHANGED | com.kaiten.instance.billing.v1.provider_changed | A subscription moves to another payment provider |
INSTANCE_UPDATED, with licenseChangeReason: BILLING_FALLBACK, records the fallback move on the instance's own audit trail; the trial reminder is recorded there too, and the other subscription events for the organization. The event catalog has every payload.
A subscription moves its instance to another License at three moments only, each on purpose: a move to another price, at its boundary; the end of the subscription, to the fallback; and a checkout, to the version it bought — once paid, when something is due. A payment state — past due included — never changes what the instance may use.
Where to go next
What each boundary bills, and how an invoice is settled: Invoices. Who collects it: Stripe or no provider. How a customer subscribes from your website or product: Self-serve checkout. Exact reference: the billing tables of the data model.
Pricing Tiers
Build a tier from one catalog of entitlements, one License per tier, and one grant per pair, then price it and list it — the Kaiten Sushi Shop tiers as the worked example, every call verified against the API.
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.