Usage Records
App Billing
Usage Records
Record metered (usage-based) charges and inspect the current cap, accrued spend, and remaining headroom for an installation.
POST
Usage Records
The usage endpoints let an installed app charge per-event — one
credit per SMS sent, per AI generation, per shipping label printed —
on top of a flat monthly subscription. Each call records meter ticks for
the current billing period under the app’s metered Stripe subscription
item.
The metered component is declared on the app’s pricing plan
(
The
Open
pricing.usage in app.json, see
Usage Billing). Every usage event you POST is
reported to Stripe behind a unique subscription_item.id, the cost
(quantity × usageUnitAmount) is checked against the merchant-approved
cap, and a USAGE billing transaction is recorded so it
surfaces in the merchant’s billing detail page.
Auth: app access token (OAuth). The endpoint resolves the
installation from the token’s aud claim — apps never pass
installationId in the body.
Usage is checked against the cap before the Stripe call. If the
projected accrual would exceed the cap, the endpoint returns
402
with code: USAGE_CAP_EXCEEDED and no usage record is created. Apps
should treat that response as a hard signal to either stop the
underlying action or call POST /usage/cap to request a raise.Record a usage event
POST /api/v1/billing/usage
integer
required
Integer number of units consumed by this event. Must be
>= 1. Reject
fractional values — scale your metered unit instead (tokens rather
than kilo_tokens).string
Optional client-supplied key. Forwarded to Stripe as the
subscriptionItems.createUsageRecord idempotency key (namespaced to
the installation so collisions across apps are impossible). Retries
with the same key are always safe; without one, double-billing is
possible on network retries.Response
string
ISO timestamp Stripe used for the usage record.
integer
Echo of the recorded quantity.
number
Per-unit price from the app’s pricing plan (dollars).
integer
Cost of this event (
quantity × unitAmount × 100).integer
Running total accrued in the current billing period after this event.
integer | null
Merchant-approved cap in cents (
null if uncapped).integer | null
Cap headroom after this event (
null if uncapped).string
Stripe
mbur_… usage-record id (also stored on the billing transaction).Cap exceeded
When the projected accrual would exceed the cap, the endpoint returns402 and no usage is recorded:
message field is a JSON-encoded payload — parse it client-side to
decide how to respond. code: USAGE_CAP_EXCEEDED is stable; the cents
fields let you compute how much headroom is left and prompt the
merchant to raise the cap via POST /usage/cap.
Get current usage state
GET /api/v1/billing/usage
Returns the metered configuration plus the current period’s accrual.
Apps typically call this from their admin home iframe to render a
“used X of Y this month” progress bar.
Response
string | null
Human label from the pricing plan, e.g.
SMS, email, AI generation.number | null
Per-unit price in dollars.
integer | null
Merchant-approved cap in cents (
null if uncapped).integer
Cents accrued so far this billing period.
integer | null
Cap headroom (
null if uncapped).string | null
ISO timestamp when the current Stripe period ends.
accruedAmountCents resets to 0 here.string | null
Stripe
si_… subscription-item id this usage attaches to.Update the usage cap
POST /api/v1/billing/usage/cap
The cap is the merchant’s per-period spending ceiling on the metered
component. It defaults to the value declared in pricing.usage.cappedAmount
when the merchant first subscribes, and can be changed afterwards:
- Lowering the cap takes effect immediately, but cannot go below
what the merchant has already accrued this period (would create an
instant
USAGE_CAP_EXCEEDEDloop). - Raising the cap requires merchant approval. The endpoint returns
a Stripe Billing Portal
confirmationUrlthe merchant must visit to authorise the new cap; the row isn’t updated until they confirm.
number
required
Requested cap in dollars (integer cents on the wire is fine —
values are rounded).
0 is allowed and effectively disables further
usage charges until the cap is raised.string
URL the merchant returns to after confirming a raise in the Stripe
Billing Portal. Defaults to the app developer’s the admin origin.
Response — immediate (lowering)
Response — approval required (raising)
confirmationUrl in a top-level browser tab (don’t iframe — Stripe
blocks framing the billing portal). After the merchant confirms, the
backend updates the row; the next POST /usage call sees the new cap.
Idempotency guidelines
- Mint a stable
idempotencyKeyper real-world event. A message-id, a generation-id, or a UUIDv4 you record alongside the event all work. - Keys are namespaced server-side as
usage:{installationId}:{key}and passed to Stripe as the canonical idempotency key. Stripe retains idempotency keys for 24 h, so retries within that window are safe. - Without a key, network retries can produce duplicate
UsageRecordrecords on Stripe and duplicate billing transactions (the unique index onstripeUsageRecordIdcatches the second insert, but you’ve still double-billed the merchant on Stripe’s side).
Error codes
See also
- Usage Billing — conceptual guide to declaring
metered pricing in
app.jsonand the full event → invoice flow. - Subscriptions — flat monthly subscription state.
- Transactions — every billing transaction (flat + usage + invoice).