Billing

Adjustments, refunds, invoice lifecycle, billing up front, the wallet, and user suspension. Endpoint signatures: HTTP API.

Adjustments and refunds

Manual billing corrections are first-class events, not embedded fields on other records. POST /adjustments appends adjustment.posted; amounts are signed integers (negative = credit, positive = charge) with no currency code. kind is prorate, credit, or debit (inferred from sign if omitted for manual entries). Plan changes with refund_immediately append refund.issued automatically — refund amount is always positive; the invoice line is negative. Query history with GET /adjustments and GET /refunds.

Invoice lifecycle

Invoices move through explicit states: draft → finalized → paid, or void from draft/finalized.

  1. POST /invoices/generate — draft with line items for any billing window
  2. POST /invoices/{id}/finalize — lock amounts before collection
  3. POST /invoices/{id}/apply-wallet — optional: pay part or all of it from the wallet
  4. POST /invoices/{id}/mark-paid — record payment with payment_ref
  5. POST /invoices/{id}/void — cancel an unpaid invoice

Events: invoice.generated, invoice.finalized, invoice.paid, invoice.voided. Replay restores status, timestamps, and payment_ref.

Generation semantics: subscription periods reconstructed from the event log; base fee proportional to active time (trials and pauses excluded); plan segments billed separately; billing_interval: year applies annual discount; usage scaled for partial windows; metric groups and usage pools share included across members; included_rollover adds unused included from the previous period at invoice time; the same metric across multiple subscriptions for one user is billed only once; built-in JSON billing or Luau pricing. JSON classify runs only at usage ingest.

Billing up front

A Luau plan can bill a period up front with advance(ctx) — seats, a platform fee — and its arrears at the end with pricing(ctx). The advance lines go on the invoice whose window contains the period's start (window start exclusive, window end inclusive), so one invoice per closed period carries both:

  • the invoice for [P.start, P.end] = arrears of P + advance of the next period, which starts at P.end;
  • the first period is billed by a window that ends at the subscription start, e.g. [start − 1s, start];
  • a subscription canceled at period end has no next period, so no advance.

Gauge readings such as the seat count come from latest(ctx, code): the last reported quantity at or before the period start. Details and an example: Luau pricing ctx.

Wallet (prepaid balance)

Each user has a wallet: money paid up front that invoices draw from — prepaid usage, credits bought in advance, a top-up that covers overage. HUME does not take payments; your app charges the card and credits the wallet once the payment provider confirmed it.

  1. POST /users/{id}/wallet/credit — a paid top-up (amount in billing units, the PSP reference); the payment id as Idempotency-Key makes a retried webhook harmless
  2. GET /users/{id}/wallet — the balance and its entries; use it for your own gates (e.g. how much overage the balance still covers)
  3. POST /invoices/{id}/apply-wallet — pays a draft or finalized invoice from the balance: min(balance, amount due, max_amount); max_amount limits it to, say, the usage lines
  4. collect total − wallet_applied with your PSP (nothing, if the wallet covered it), then mark-paid

The invoice keeps its total; wallet_applied shows the part the wallet paid. Events: wallet.credited, wallet.debited — they reach webhooks and replay like any other. Endpoint signatures: HTTP API → Wallet.

User block, unblock, and delete

Blocking is for non-payment of your customers: the user can still send POST /usage and StatsD events (metering continues), but your app must treat them as suspended. Invoice APIs are tenant-admin — generate, finalize, mark-paid, and void work while a user is blocked so dunning can record payment without unblocking first.

OperationBlocked userDeleted user
POST /usage, StatsDallowedrejected
Entitlements, usage list, subscription GET, adjustments403 blocked403 deleted
Invoice generate / read / finalize / mark-paid / voidallowed (tenant admin)generate rejected; existing invoices readable
GET /users, GET /users/{id}, ?external_id=listed / returned with flagshidden from list; GET by id still works
Unblockyesno (permanent)

Events: user.blocked, user.unblocked, user.deleted.

Tenant invoice suspension (platform non-payment)

When a HUME project is past the unpaid grace period, the site suspends POST /invoices/generate on that tenant via POST /tenant/suspend-invoices. Metering, plans, and webhooks keep working. After payment: POST /tenant/resume-invoices. Inspect with GET /tenant (invoices_suspended).