Skip to main content

Usage-Based Billing

Usage-based (a.k.a. metered) billing charges merchants per real-world event — per SMS sent, per AI generation, per shipping label printed — on top of an optional flat monthly subscription. Apps report meter ticks to LaunchMyStore via POST /api/v1/billing/usage and the platform forwards them to Stripe as usage records under the merchant’s subscription. LaunchMyStore implements metered billing as a second Stripe subscription item on the merchant’s existing app subscription. Your app declares one metered unit per pricing plan; the platform handles Stripe wiring, cap enforcement, and per-event accounting.

How it works

Two key invariants:
  1. The cap is checked before the Stripe call. If the projected accrual exceeds the cap, the platform returns 402 with code: USAGE_CAP_EXCEEDED and never calls Stripe. Your app sees no side effect and gets the remaining headroom in cents.
  2. stripeUsageRecordId is unique. Every UsageRecord from Stripe produces exactly one billing transaction. Stripe idempotency keys + a database unique index mean that even network retries can’t double-bill.

Declaring metered pricing

Add a usage block to your app’s pricing in app.json:
{
  "handle": "smart-sms",
  "name": "Smart SMS",
  "pricing": {
    "model": "recurring",
    "monthlyPrice": 10,
    "yearlyPrice": 100,
    "currency": "usd",
    "trialDays": 14,
    "usage": {
      "unitName": "SMS",
      "unitAmount": 0.05,
      "cappedAmount": 50,
      "terms": "$0.05 per SMS sent, billed monthly"
    }
  }
}
FieldTypeRequiredMeaning
unitNamestringyesHuman label shown to merchants (e.g. SMS, AI generation).
unitAmountnumberyesPer-unit price in dollars (0.05 = 5¢ per unit).
cappedAmountintegeryesDefault monthly cap in dollars. Merchant can raise or lower in the billing portal.
termsstringnoFree-form text shown at subscribe time.
When the merchant subscribes, the backend:
  1. Creates (or reuses) a metered Stripe Price with recurring.usage_type='metered', aggregate_usage='sum'.
  2. Adds it as a second line_items entry on the Checkout session — the merchant sees the flat fee + the per-unit price in one approval.
  3. After Checkout completes, the platform walks the subscription items, locates the metered one, and stores its id on the installation so usage records can attach to it.
pricing.model may be recurring, one_time, or free — only recurring plans can carry a usage block.

Reporting usage

Your app POSTs one event per real-world action. The token’s installation is the one billed; quantity is the count of units consumed.
// Inside your app — server-side or App Bridge–authenticated request
const r = await fetch('https://api.launchmystore.io/api/v1/billing/usage', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${appAccessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    quantity: 1,
    idempotencyKey: `sms-${messageId}`,
  }),
});

if (r.status === 402) {
  const body = await r.json();
  const meta = JSON.parse(body.message); // { code, capCents, accruedCents, remainingCents }
  if (meta.code === 'USAGE_CAP_EXCEEDED') {
    // Refuse the underlying action; prompt merchant to raise cap.
    return showCapExceededModal(meta);
  }
}
Full request / response shapes: Usage Records API.

Idempotency

Pass an idempotencyKey per event. The platform namespaces it as usage:{installationId}:{key} and forwards it as Stripe’s idempotency key — retries within 24 hours are guaranteed safe. Stable choices:
  • A message-id, generation-id, or shipping-label-id you already mint.
  • A UUIDv4 you record alongside the event in your own DB.
Without a key, a single network retry can double-bill.

Batching

The platform doesn’t aggregate usage — each POST /usage is one Stripe API call and one UsageRecord. For high-frequency events (an analytics app recording one row per page view, say) buffer client-side and POST periodic batches with quantity: <sum>. Stripe sums all UsageRecord.quantity for a subscription_item over the billing period; one batched call costs the same as N individual calls.

Reading current state

GET /api/v1/billing/usage returns the current cap, accrued spend, remaining headroom, and the period-end timestamp. Apps render this on their admin home iframe to show “used 6of6 of 50 this month”.
const r = await fetch('https://api.launchmystore.io/api/v1/billing/usage', {
  headers: { Authorization: `Bearer ${appAccessToken}` },
});
const { data } = await r.json();
// {
//   unitName: 'SMS',
//   unitAmount: 0.05,
//   capAmountCents: 5000,
//   accruedAmountCents: 605,
//   remainingCents: 4395,
//   currentPeriodEnd: '2026-06-01T00:00:00.000Z',
//   meteredItemId: 'si_OqW2NABCxyz'
// }

Cap management

Caps protect merchants from bill shock — once monthly usage cost exceeds the cap, further events 402 until the next period or until the merchant raises the cap.

Lowering the cap

Lowering is immediate. The new value cannot be below what the merchant has already accrued this period (would create an instant cap loop).
curl -X POST .../api/v1/billing/usage/cap \
  -H "Authorization: Bearer …" \
  -d '{"cappedAmount": 10}'
# 200 { requiresApproval: false, newCap: 10 }

Raising the cap

Raising requires merchant approval. The endpoint returns a Stripe Billing Portal confirmationUrl; the merchant opens it in a top-level tab, confirms the new cap, and returns to your returnUrl. The cap row is updated only after they confirm.
curl -X POST .../api/v1/billing/usage/cap \
  -H "Authorization: Bearer …" \
  -d '{"cappedAmount": 100, "returnUrl": "https://my-app.example.com/billing/return"}'
# 200 { requiresApproval: true, confirmationUrl: 'https://billing.stripe.com/p/session/...', currentCap: 50, requestedCap: 100 }
Open confirmationUrl at the top level — Stripe blocks framing the billing portal. After return, call GET /usage to confirm the cap took effect.

Default cap

If you don’t set pricing.usage.cappedAmount, the platform treats the installation as uncapped. GET /usage returns capAmountCents: null and remainingCents: null; cap-exceeded 402s never fire. Recommended only for apps where the per-unit cost is tiny and bill shock is genuinely impossible.

Billing period end

When the Stripe billing period rolls over:
  1. Stripe sums all UsageRecord.quantity for the metered subscription item, multiplies by unit_amount, and adds the result as a line on the merchant’s invoice.
  2. The merchant pays the invoice (auto-charged via saved payment method).
  3. Stripe fires the invoice.paid webhook.
  4. The platform records a metered_invoice billing transaction reflecting the period’s metered total, then resets the installation’s accrued-usage counter to 0.
Your app does nothing — accrued counters reset automatically and the next event starts a fresh period. Visible to merchants in the billing detail page as a single “Usage — May 2026” line item.

Pricing strategy

StrategyWhen to use
Flat only (no usage)Predictable workloads — page builders, theme apps.
Metered onlyPure pay-as-you-go — analytics, AI inference, transactional SMS.
Flat + meteredMost apps. Flat covers fixed costs; metered handles variable cost.
High unitAmount, low cappedAmountPremium per-event apps (e.g. one 5AIimagegeneration,cap5 AI image generation, cap 50/mo).
Low unitAmount, high cappedAmountHigh-volume apps (e.g. 0.001perevent,cap0.001 per event, cap 1000/mo).
Tiered (graduated) pricing — where the first 100 units are free and later units get cheaper — is not yet implemented as declarative metadata. To approximate it, set unitAmount to the average effective price and rely on your pricing.terms field for human-readable detail. Native graduated billing is on the roadmap.

Best practices

Without one, a single retry double-bills the merchant. The key should map to a real-world event (sms-{msg_id}, generation-{uuid}), not a wall-clock timestamp.
Call GET /usage from your admin home iframe and render a progress bar (accruedAmountCents / capAmountCents). Merchants want to see what they’re spending before the invoice arrives.
A USAGE_CAP_EXCEEDED response means the merchant has spent their monthly cap. Refuse the underlying action and surface a clear “Cap reached — raise to keep sending” CTA that calls POST /usage/cap. Don’t silently retry.
SMS sent is clearer than credits. Merchants understand a unit they recognise from your product — copy the language your existing pricing page uses.
Set cappedAmount to an amount your typical merchant won’t hit but your runaway scripts will. A SMS app might cap at $50 — enough for a small store, low enough that an infinite loop doesn’t bankrupt the merchant before someone notices.
The platform doesn’t aggregate — one POST per event is one Stripe call. For per-impression analytics or per-byte storage, sum client-side and POST every minute.

Common metered units

UnitApps that use it
SMS sentSMS marketing, OTP, transactional SMS
email sentEmail marketing apps on top of flat sender quota
AI generationProduct description, image, blog-post generators
shipping labelShipping apps charging per label printed
webhook deliveredEvent-stream / pipeline apps
MB syncedInventory / catalog sync apps
API callHeadless storefront / GraphQL bridge apps

See also