Owostack
Guides

Usage-based billing

Track metered usage and enforce limits with check() and track()

How to implement usage-based billing

This guide shows how to meter usage against a quota, enforce limits, and handle overages.

Define a metered feature

In the dashboard, create a feature with type Metered. Then attach it to a plan with a limit and reset interval.

Or define it in code:

import { metered, plan } from "@owostack/core";

const apiCalls = metered("api-calls", { name: "API Calls" });

plan("pro", {
  name: "Pro",
  price: 2000,
  currency: "USD",
  interval: "monthly",
  features: [
    apiCalls.limit(10000, { reset: "monthly" }),
  ],
});

The counter resets to zero at the start of each billing period.

Gate requests with check()

Before performing an expensive operation, verify the customer has remaining quota:

const access = await owo.check({
  customer: "user_123",
  feature: "api-calls",
});

if (!access.allowed) {
  console.log("Quota exceeded", access.balance, access.resetsAt);
}

// Proceed with the operation...

Record usage with track()

After a successful operation, record the usage:

const result = await owo.track({
  customer: "user_123",
  feature: "api-calls",
  value: 1,
});

// result.allowed — whether the usage was accepted
// result.balance — remaining quota after this usage

Check-then-track pattern

The most common pattern combines both calls:

async function handleApiRequest(userId: string) {
  // 1. Check quota
  const access = await owo.check({
    customer: userId,
    feature: "api-calls",
  });

  if (!access.allowed) {
    throw new Error(`Quota exceeded. Resets at ${access.resetsAt}`);
  }

  // 2. Do the work
  const result = await doExpensiveWork();

  // 3. Track usage (only after success)
  await owo.track({
    customer: userId,
    feature: "api-calls",
    value: 1,
  });

  return result;
}

Track variable amounts

Not all operations cost one unit. Pass a custom value:

// AI token usage — variable cost per request
await owo.track({
  customer: "user_123",
  feature: "ai-tokens",
  value: response.usage.total_tokens,
});

// Storage — track in MB
await owo.track({
  customer: "user_123",
  feature: "storage-mb",
  value: fileSizeInMb,
});

Enable overages

To charge customers for usage beyond their limit instead of blocking them, set the overage policy on the feature:

apiCalls.limit(10000, {
  overage: "charge",
  overagePrice: 50, // price per unit in minor currency
  maxOverageUnits: 100000, // optional hard cap
});

With overage: "charge", track() continues to accept usage beyond the limit and returns code: "tracked_overage". The overage amount is billed at the end of the period.

Reset intervals

How often the usage counter resets:

  • monthly — resets on the subscription billing cycle anchor
  • weekly — resets every week
  • daily — resets every day
  • never — usage accumulates indefinitely (good for lifetime limits)

Rollover

Allow unused usage to carry over to the next period:

apiCalls.limit(10000, {
  reset: "monthly",
  rollover: true,
  maxRollover: 20000, // cap at 2x monthly limit
});

Show usage in your UI

Use check() to display a usage bar or remaining count:

const usage = await owo.check({
  customer: "user_123",
  feature: "api-calls",
});

// In your UI:
// Used: (usage.limit - usage.balance) / usage.limit * 100%
// Remaining: usage.balance
// Resets: usage.resetsAt

On this page