Metered usage
Charge customers based on the usage they actually consume.
For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: text/markdown.
Metered usage billing lets you charge customers for what they actually use, such as API calls, AI tokens, storage, generated images, or processed jobs.
TurboStarter supports metered billing for recurring subscriptions:
- define a billing variant as
BillingType.METERED - let customers subscribe through the normal checkout flow
- report billable usage from trusted server-side code
- query usage later for billing screens or internal checks
How it works
Metered billing follows a simple pattern:
A customer subscribes to a metered recurring plan.
Your app tracks billable usage on the backend.
Your server reports that usage to the billing provider.
The provider aggregates usage and bills the customer for the billing period.
This works especially well when your product usage changes over time and a flat subscription would be too rigid.
Configuration
Metered usage is configured like any other billing variant, but with type: BillingType.METERED.
export const config = billingConfigSchema.parse({
plans: [
{
id: BillingPlan.PREMIUM,
name: "Premium",
description: "Best for usage-based products",
badge: "Popular",
features: [
"Unlimited projects",
"Usage-based billing",
"Priority support",
],
variants: [
{
id: "price_monthly_metered",
meterId: "mtr_your_meter_id",
type: BillingType.METERED,
unit: "credit",
model: BillingModel.RECURRING,
interval: RecurringInterval.MONTH,
trialDays: 7,
tiers: [
{ cost: 8, upTo: 25_000 },
{ cost: 6, upTo: 125_000 },
{ cost: 4 },
],
},
],
},
],
}) satisfies BillingConfig;Let's break down the fields:
id: The provider-specific price, variant, or product ID. This must match your billing provider exactly.type: Must beBillingType.METERED.meterId: The provider meter identifier used when querying usage.unit: The unit shown in your UI, such ascredit,request, ortoken.model: Must beBillingModel.RECURRING.interval: Required for metered billing.trialDays: Optional trial length.cost: Use this for a simple flat usage rate.tiers: Use this for tiered usage pricing.
Metered billing is recurring-only
Metered variants cannot use BillingModel.ONE_TIME. The billing schema validates this for you.
Fixed usage pricing
Use cost when every unit should cost the same amount.
{
id: "price_monthly_metered",
meterId: "mtr_your_meter_id",
type: BillingType.METERED,
unit: "request",
model: BillingModel.RECURRING,
interval: RecurringInterval.MONTH,
cost: 5,
}Tiered usage pricing
Use tiers when you want the unit price to change as usage grows.
{
id: "price_monthly_metered",
meterId: "mtr_your_meter_id",
type: BillingType.METERED,
unit: "credit",
model: BillingModel.RECURRING,
interval: RecurringInterval.MONTH,
tiers: [
{ cost: 8, upTo: 25_000 },
{ cost: 6, upTo: 125_000 },
{ cost: 4 },
],
}This works well for patterns like:
- charging the same amount for every API call or token
- cheaper unit pricing at higher usage volumes
- including an initial amount of usage at a lower or zero rate
Checkout
Metered variants use the normal recurring checkout flow. Unlike per-seat billing, TurboStarter does not send a quantity during checkout for metered plans.
That is because metered billing is based on usage reported later, not on an upfront seat count.
In practice, this means:
- Checkout creates the subscription
- Your app reports usage after billable work happens
- The provider calculates the final bill from reported usage
Reporting usage
Usage reporting should happen in trusted server-side code only.
That can be:
- an API route
- a server action
- a background job
- a queue worker
Do not report usage directly from the browser
Usage affects invoices, so the backend should be the source of truth.
Example flow
Identify the billing reference for the user or organization.
Resolve the provider customer connected to that reference.
Perform the billable work.
Report the usage amount to the billing provider.
import { getCustomersByReferenceId } from "@workspace/billing/server";
import { recordUsage } from "@workspace/billing-web/server";
export const reportCreditsUsage = async ({
referenceId,
quantity,
}: {
referenceId: string;
quantity: number;
}) => {
const [customer] = await getCustomersByReferenceId(referenceId);
if (!customer) {
return { recorded: false };
}
return recordUsage({
externalId: customer.externalId,
quantity,
event: "credits_used",
});
};In this example:
referenceIdis the user or organization being billedexternalIdis the provider customer ID stored by TurboStartereventis used by providers that track usage through meter events
Keep usage reporting idempotent when possible
If the same billable action can be retried, make sure your own backend logic avoids double-reporting usage.
Querying usage
TurboStarter also supports querying aggregated usage. This is useful when you want to:
- show current usage in billing settings
- show usage during a trial or billing period
- validate internal dashboards or support workflows
import { getCustomersByReferenceId } from "@workspace/billing/server";
import { getUsage } from "@workspace/billing-web/server";
export const getCurrentUsage = async ({
referenceId,
meterId,
start,
end,
}: {
referenceId: string;
meterId: string;
start: Date;
end: Date;
}) => {
const [customer] = await getCustomersByReferenceId(referenceId);
if (!customer) {
return { usage: 0, start, end };
}
return getUsage({
externalId: customer.externalId,
meterId,
start,
end,
});
};The billing UI can use this to show usage for the active subscription period.
Provider notes
Metered billing works across the supported web billing providers, but the way usage is reported differs slightly.
- Stripe: reports billing meter events for a customer and queries usage through the configured
meterId - Lemon Squeezy: records usage against the active subscription item and returns the current period usage from that item
- Polar: ingests meter events for a customer and queries aggregated totals through the configured
meterId
The two IDs that matter most are:
variant.id: the provider price, variant, or product ID used for checkoutmeterId: the provider meter identifier used for querying usage
Make sure both match the correct objects in your billing provider.
Discounts
Metered variants support cost and tiers, so TurboStarter can still describe the pricing model in the UI.
One important difference from flat and per-seat recurring plans is that automatic recurring discount comparison is not applied to metered variants in the same way. In most cases, your metered plan pricing should be explained directly through the variant pricing itself.
Testing
Before shipping, test the full flow:
- Subscribe to a metered plan.
- Trigger a billable action from your app.
- Verify that usage is reported successfully from server-side code.
- Query usage for the current period and confirm it matches what you expect.
- Check the billing provider dashboard to make sure usage and invoicing look correct.
If something looks off, the most common causes are:
- the variant is missing
type: BillingType.METERED - the
meterIdis missing or incorrect - usage is being reported from the wrong billing reference
- the provider customer does not exist yet for that reference
- your backend is reporting usage twice for the same billable action
Recommended setup
For most usage-based SaaS products, the simplest setup is:
- Create a recurring metered variant with a clear
unit. - Configure the matching metered price in your billing provider.
- Add a
meterIdto your billing config. - Report usage only from trusted server-side code.
- Query usage for the current billing period anywhere you want to show progress or billing context.
This gives you a clean model: TurboStarter handles subscription checkout and billing state, while your application decides what counts as billable usage and when to report it.
How is this guide?
Last updated on