> ## 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.

# Usage Records

> Record metered (usage-based) charges and inspect the current cap, accrued spend, and remaining headroom for an installation.

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
(`pricing.usage` in `app.json`, see
[Usage Billing](/billing/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 |

<Note>
  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.
</Note>

***

## Record a usage event

`POST /api/v1/billing/usage`

<ParamField body="quantity" type="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`).
</ParamField>

<ParamField body="idempotencyKey" type="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.
</ParamField>

```bash theme={null}
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

<ResponseField name="data.recordedAt" type="string">ISO timestamp Stripe used for the usage record.</ResponseField>
<ResponseField name="data.quantity" type="integer">Echo of the recorded quantity.</ResponseField>
<ResponseField name="data.unitAmount" type="number">Per-unit price from the app's pricing plan (dollars).</ResponseField>
<ResponseField name="data.amountCents" type="integer">Cost of this event (`quantity × unitAmount × 100`).</ResponseField>
<ResponseField name="data.accruedAmountCents" type="integer">Running total accrued in the current billing period **after** this event.</ResponseField>
<ResponseField name="data.capAmountCents" type="integer | null">Merchant-approved cap in cents (`null` if uncapped).</ResponseField>
<ResponseField name="data.remainingCents" type="integer | null">Cap headroom after this event (`null` if uncapped).</ResponseField>
<ResponseField name="data.stripeUsageRecordId" type="string">Stripe `mbur_…` usage-record id (also stored on the billing transaction).</ResponseField>

```json theme={null}
{
  "status": 200,
  "state": "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:

```json theme={null}
{
  "status": 402,
  "state": "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

<ResponseField name="data.unitName" type="string | null">Human label from the pricing plan, e.g. `SMS`, `email`, `AI generation`.</ResponseField>
<ResponseField name="data.unitAmount" type="number | null">Per-unit price in dollars.</ResponseField>
<ResponseField name="data.capAmountCents" type="integer | null">Merchant-approved cap in cents (`null` if uncapped).</ResponseField>
<ResponseField name="data.accruedAmountCents" type="integer">Cents accrued so far this billing period.</ResponseField>
<ResponseField name="data.remainingCents" type="integer | null">Cap headroom (`null` if uncapped).</ResponseField>
<ResponseField name="data.currentPeriodEnd" type="string | null">ISO timestamp when the current Stripe period ends. `accruedAmountCents` resets to 0 here.</ResponseField>
<ResponseField name="data.meteredItemId" type="string | null">Stripe `si_…` subscription-item id this usage attaches to.</ResponseField>

```bash theme={null}
curl -X GET "https://api.launchmystore.io/api/v1/billing/usage" \
  -H "Authorization: Bearer <APP_ACCESS_TOKEN>"
```

```json theme={null}
{
  "status": 200,
  "state": "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.

<ParamField body="cappedAmount" type="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.
</ParamField>

<ParamField body="returnUrl" type="string">
  URL the merchant returns to after confirming a raise in the Stripe
  Billing Portal. Defaults to the app developer's the admin origin.
</ParamField>

```bash theme={null}
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)

```json theme={null}
{
  "status": 200,
  "state": "success",
  "data": {
    "requiresApproval": false,
    "newCap": 8
  }
}
```

### Response — approval required (raising)

```json theme={null}
{
  "status": 200,
  "state": "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`
  records on Stripe and duplicate billing transactions
  (the unique index on `stripeUsageRecordId` catches 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](/billing/usage-billing) — conceptual guide to declaring
  metered pricing in `app.json` and the full event → invoice flow.
* [Subscriptions](/api-reference/app-billing/subscriptions) — flat
  monthly subscription state.
* [Transactions](/api-reference/app-billing/transactions) — every
  billing transaction (flat + usage + invoice).
