Billing Primitives
Guapocado has a small vocabulary. Learn these primitives before choosing a framework integration.
Product
Section titled “Product”A product is something a customer can be on or buy.
Examples:
freeproenterpriseapi-credit-packlifetime-upgrade
Products can be unpriced, recurring, or one-time.
{ key: "pro", name: "Pro", pricing: { mode: "recurring", type: "flat", amount: 4900, currency: "usd", frequency: "month", }, entitlements: { "advanced-analytics": true, "api-calls": { included: 100000 }, seats: { included: 10 }, },}Use product keys in your app, not Stripe IDs.
Feature
Section titled “Feature”A feature answers a yes/no question.
Examples:
advanced-analyticsssoaudit-logspremium-support
Config:
entitlements: { "advanced-analytics": { type: "feature" },},products: [ { key: "pro", entitlements: { "advanced-analytics": true, }, },],Runtime:
if (!(await guap.has("advanced-analytics"))) { throw new Response("Upgrade required", { status: 403 });}Use a feature when there is no quantity and no consumption.
A meter tracks usage that Guapocado records.
Examples:
api-callsai-creditsemails-senttranscription-minutes
Config:
entitlements: { "ai-credits": { type: "meter", reset: "monthly" },},products: [ { key: "pro", entitlements: { "ai-credits": { included: 40000 }, }, },],Runtime:
const balance = await guap.usage.balance("ai-credits");
if (balance.balance < 250 && !balance.overageEnabled) { throw new Response("Not enough credits", { status: 402 });}
await guap.usage.consume("ai-credits", 250);Use a meter when the billable action happens as an event: a request, a model call, a generated report, a sent email.
A limit returns a numeric allowance. Your app compares that allowance with state it already owns.
Examples:
seatsprojectsworkspacesconnected-accounts
Config:
entitlements: { seats: { type: "limit" },},products: [ { key: "pro", entitlements: { seats: { included: 10 }, }, },],Runtime:
const seats = await guap.limit("seats");const currentSeats = await db.user.count({ where: { orgId } });
if (currentSeats >= seats.limit) { throw new Response("Seat limit reached", { status: 403 });}Use a limit when the source of truth is your app database.
Overage
Section titled “Overage”Overage belongs to meters. It lets usage continue beyond the included allowance when the plan allows it and the customer enables it.
"ai-credits": { included: 40000, overage: { allowed: true, unit: 10000, amount: 500, currency: "usd", },}Then customers can opt in from your server:
await guap.usage.configure("ai-credits", { overageEnabled: true,});Enabling overage adds a metered Stripe subscription item to the customer’s
subscription. Each consume() call that crosses the included balance reports
incremental usage to Stripe. If overage is disabled, consume() fails when the
customer runs out.
The plan must define overage.allowed: true with a price. If the plan has no
overage pricing configured, configure({ overageEnabled: true }) returns an
error.
Expansion
Section titled “Expansion”Expansion belongs to limits. It lets a customer purchase more of a numeric allowance.
seats: { included: 10, expansion: { allowed: true, unit: 1, amount: 1200, currency: "usd", },}Call limits.configure from your server to record the purchase:
await guap.limits.configure("seats", { purchased: 3, autoExpansionEnabled: false,});
const seats = await guap.limit("seats");seats.limit; // 13Calling limits.configure with a purchased value adds or updates a licensed
Stripe subscription item at the corresponding quantity (purchased / unit).
If the plan has no expansion pricing configured, the call returns an error.
There is no free expansion — if pricing is not defined, the only way to raise
the limit is to upgrade the plan.
Checkout
Section titled “Checkout”Checkout starts payment for a product.
const checkout = await guap.checkout.create({ productKey: "pro", successUrl: "https://app.example.com/billing/success", cancelUrl: "https://app.example.com/billing",});Guapocado reads the product config and creates the right Stripe Checkout mode:
- recurring product: subscription checkout
- one-time product: payment checkout
Context
Section titled “Context”Context fetches several answers at once.
const context = await guap.context({ features: ["advanced-analytics"], usage: ["ai-credits"], limits: ["seats"],});
context.features["advanced-analytics"];context.usage["ai-credits"]?.balance;context.limits.seats?.limit;Use context when a page, API route, or layout needs a bundle of billing state.