AI Credits
AI products often need a credit system because different model calls cost different amounts.
Guapocado models this as a meter.
Config
Section titled “Config”import { defineBilling } from "@guapocado/sdk";
export default defineBilling({ entitlements: { "ai-credits": { type: "meter", reset: "monthly", }, "premium-models": { type: "feature", }, }, products: [ { key: "starter", pricing: { mode: "recurring", type: "flat", amount: 1900, currency: "usd", frequency: "month", }, entitlements: { "premium-models": false, "ai-credits": { included: 10000 }, }, }, { key: "pro", pricing: { mode: "recurring", type: "flat", amount: 4900, currency: "usd", frequency: "month", }, entitlements: { "premium-models": true, "ai-credits": { included: 40000, overage: { allowed: true, unit: 10000, amount: 500, currency: "usd", }, }, }, }, { key: "credit-pack", pricing: { mode: "one_time", type: "flat", amount: 1900, currency: "usd", }, entitlements: { "ai-credits": { included: 25000 }, }, }, ],});This gives you:
- monthly included credits on subscription plans
- optional paid overage on Pro
- one-time credit packs
- a separate feature gate for premium models
Estimate Cost Before the Call
Section titled “Estimate Cost Before the Call”function estimateCredits(inputTokens: number, outputTokens: number) { return Math.ceil(inputTokens / 1000) + Math.ceil(outputTokens / 1000) * 4;}Your credit formula is product logic. Keep it close to the feature using it.
Enforce and Consume
Section titled “Enforce and Consume”import { createGuapocadoClient } from "@guapocado/sdk";
export async function runCompletion({ customerId, input,}: { customerId: string; input: string;}) { const guap = createGuapocadoClient({ apiKey: process.env.GUAPOCADO_API_KEY!, customerId, });
const estimatedCredits = estimateCredits(input.length / 4, 1000); const balance = await guap.usage.balance("ai-credits");
if (balance.balance < estimatedCredits && !balance.overageEnabled) { throw new Error("Not enough AI credits"); }
await guap.usage.consume("ai-credits", estimatedCredits);
try { return await callModel(input); } catch (error) { await guap.usage.refund("ai-credits", estimatedCredits); throw error; }}Gate Premium Models
Section titled “Gate Premium Models”if (model.startsWith("premium-") && !(await guap.has("premium-models"))) { throw new Error("Upgrade required for premium models");}Use a feature for the model family and a meter for credits. Do not make model access a credit balance.
Show Usage in React
Section titled “Show Usage in React”import { useEntitlement, useUsageBalance } from "@guapocado/react";
export function AiUsagePanel() { const premiumModels = useEntitlement("premium-models"); const credits = useUsageBalance("ai-credits");
if (credits.loading) return <span>Loading credits...</span>;
return ( <section> <p>{credits.balance} credits remaining</p> <p>{premiumModels.has ? "Premium models enabled" : "Starter models only"}</p> </section> );}Consume after you have decided the work should count. Refund if the downstream AI call fails.
For streaming responses, either consume a safe estimate up front and refund the difference later, or consume after the stream completes if your UX can tolerate the failure happening at the end.