Skip to content

AI Credits

AI products often need a credit system because different model calls cost different amounts.

Guapocado models this as a meter.

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
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.

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;
}
}
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.

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.