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.
| Endpoint | Scope | Purpose |
|---|---|---|
POST /api/v1/billing/usage | write_billing | Record N units of usage |
GET /api/v1/billing/usage | read_billing | Read current cap, accrued spend, period end |
POST /api/v1/billing/usage/cap | write_billing | Lower (immediate) or raise (approval) the cap |
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 number of units consumed by this event. Must be
>= 1. Reject
fractional values — scale your metered unit instead (tokens rather
than kilo_tokens).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
ISO timestamp Stripe used for the usage record.
Echo of the recorded quantity.
Per-unit price from the app’s pricing plan (dollars).
Cost of this event (
quantity × unitAmount × 100).Running total accrued in the current billing period after this event.
Merchant-approved cap in cents (
null if uncapped).Cap headroom after this event (
null if uncapped).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
Human label from the pricing plan, e.g.
SMS, email, AI generation.Per-unit price in dollars.
Merchant-approved cap in cents (
null if uncapped).Cents accrued so far this billing period.
Cap headroom (
null if uncapped).ISO timestamp when the current Stripe period ends.
accruedAmountCents resets to 0 here.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.
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.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
| Code | When |
|---|---|
400 | quantity < 1, fractional, or non-integer. |
400 | Installation has no stripeMeteredItemId (app pricing has no usage component). |
400 | App has no usageUnitAmount configured. |
400 | cappedAmount below already-accrued spend (on /usage/cap). |
401 | Missing or invalid app access token. |
402 | Subscription not active or trialing — see billingStatus. |
402 | USAGE_CAP_EXCEEDED — see body for capCents / accruedCents / remainingCents. |
403 | Token lacks write_billing (POST) or read_billing (GET) scope. |
404 | Installation or app not found. |
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).