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 usageCheck-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 anchorweekly— resets every weekdaily— resets every daynever— 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