Skip to main content
POST
/
api
/
v1
/
billing
/
usage
Usage Records
curl --request POST \
  --url https://api.launchmystore.io/api/v1/billing/usage \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "quantity": 123,
  "idempotencyKey": "<string>",
  "cappedAmount": 123,
  "returnUrl": "<string>"
}
'
{
  "data.recordedAt": "<string>",
  "data.quantity": 123,
  "data.unitAmount": {},
  "data.amountCents": 123,
  "data.accruedAmountCents": 123,
  "data.capAmountCents": {},
  "data.remainingCents": {},
  "data.stripeUsageRecordId": "<string>",
  "data.unitName": {},
  "data.currentPeriodEnd": {},
  "data.meteredItemId": {}
}

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

The usage endpoints are the LaunchMyStore equivalent of Shopify’s appUsageRecordCreate. They 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. The metered component is declared on the app’s pricing plan (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 row is appended to AppBillingTransactions 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.
EndpointScopePurpose
POST /api/v1/billing/usagewrite_billingRecord N units of usage
GET /api/v1/billing/usageread_billingRead current cap, accrued spend, period end
POST /api/v1/billing/usage/capwrite_billingLower (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
quantity
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).
idempotencyKey
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.
curl -X POST "https://api.launchmystore.io/api/v1/billing/usage" \
  -H "Authorization: Bearer <APP_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 1,
    "idempotencyKey": "sms-msg-7c2f1c"
  }'

Response

data.recordedAt
string
ISO timestamp Stripe used for the usage record.
data.quantity
integer
Echo of the recorded quantity.
data.unitAmount
number
Per-unit price from the app’s pricing plan (dollars).
data.amountCents
integer
Cost of this event (quantity × unitAmount × 100).
data.accruedAmountCents
integer
Running total accrued in the current billing period after this event.
data.capAmountCents
integer | null
Merchant-approved cap in cents (null if uncapped).
data.remainingCents
integer | null
Cap headroom after this event (null if uncapped).
data.stripeUsageRecordId
string
Stripe mbur_… usage-record id (also written to AppBillingTransactions.stripeUsageRecordId).
{
  "status": 200,
  "type": "success",
  "data": {
    "recordedAt": "2026-05-17T18:42:11.000Z",
    "quantity": 1,
    "unitAmount": 0.05,
    "amountCents": 5,
    "accruedAmountCents": 605,
    "capAmountCents": 5000,
    "remainingCents": 4395,
    "stripeUsageRecordId": "mbur_1OqW2NABCxyz"
  }
}

Cap exceeded

When the projected accrual would exceed the cap, the endpoint returns 402 and no usage is recorded:
{
  "status": 402,
  "type": "error",
  "message": "{\"code\":\"USAGE_CAP_EXCEEDED\",\"capCents\":1000,\"accruedCents\":600,\"remainingCents\":400}"
}
The 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

data.unitName
string | null
Human label from the pricing plan, e.g. SMS, email, AI generation.
data.unitAmount
number | null
Per-unit price in dollars.
data.capAmountCents
integer | null
Merchant-approved cap in cents (null if uncapped).
data.accruedAmountCents
integer
Cents accrued so far this billing period.
data.remainingCents
integer | null
Cap headroom (null if uncapped).
data.currentPeriodEnd
string | null
ISO timestamp when the current Stripe period ends. accruedAmountCents resets to 0 here.
data.meteredItemId
string | null
Stripe si_… subscription-item id this usage attaches to.
curl -X GET "https://api.launchmystore.io/api/v1/billing/usage" \
  -H "Authorization: Bearer <APP_ACCESS_TOKEN>"
{
  "status": 200,
  "type": "success",
  "data": {
    "unitName": "SMS",
    "unitAmount": 0.05,
    "capAmountCents": 5000,
    "accruedAmountCents": 605,
    "remainingCents": 4395,
    "currentPeriodEnd": "2026-06-01T00:00:00.000Z",
    "meteredItemId": "si_OqW2NABCxyz"
  }
}

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_EXCEEDED loop).
  • Raising the cap requires merchant approval. The endpoint returns a Stripe Billing Portal confirmationUrl the merchant must visit to authorise the new cap; the row isn’t updated until they confirm.
cappedAmount
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.
returnUrl
string
URL the merchant returns to after confirming a raise in the Stripe Billing Portal. Defaults to the app developer’s TeamInfra origin.
curl -X POST "https://api.launchmystore.io/api/v1/billing/usage/cap" \
  -H "Authorization: Bearer <APP_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "cappedAmount": 100,
    "returnUrl": "https://my-app.example.com/billing/return"
  }'

Response — immediate (lowering)

{
  "status": 200,
  "type": "success",
  "data": {
    "requiresApproval": false,
    "newCap": 8
  }
}

Response — approval required (raising)

{
  "status": 200,
  "type": "success",
  "data": {
    "requiresApproval": true,
    "confirmationUrl": "https://billing.stripe.com/p/session/test_…",
    "currentCap": 50,
    "requestedCap": 100
  }
}
Open 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 idempotencyKey per 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 UsageRecord rows on Stripe and duplicate AppBillingTransactions rows (the unique index on stripeUsageRecordId catches the second insert, but you’ve still double-billed the merchant on Stripe’s side).

Error codes

CodeWhen
400quantity < 1, fractional, or non-integer.
400Installation has no stripeMeteredItemId (app pricing has no usage component).
400App has no usageUnitAmount configured.
400cappedAmount below already-accrued spend (on /usage/cap).
401Missing or invalid app access token.
402Subscription not active or trialing — see billingStatus.
402USAGE_CAP_EXCEEDED — see body for capCents / accruedCents / remainingCents.
403Token lacks write_billing (POST) or read_billing (GET) scope.
404Installation or app not found.

See also

  • Usage Billing — conceptual guide to declaring metered pricing in app.json and the full event → invoice flow.
  • Subscriptions — flat monthly subscription state.
  • Transactions — every AppBillingTransaction row (flat + usage + invoice).