Credit System Guide
Credits map model spend to something customers understand. The tables and cron hooks here track balances, burns per request, and plan resets.
Overview
Credits give you:
- Monetization: bill for tokens, not flat pages
- Cost control: hard stop before API invoices surprise you
- Plan tiers: different monthly buckets per Stripe price
How Credits Work
Credit Costs
Each AI model has a cost per 1,000 tokens:
| Model | Cost per 1k Tokens |
|---|---|
| GPT-4o Mini | 1 credit |
| GPT-4o | 5 credits |
| GPT-4 Turbo | 10 credits |
| Claude 3.5 Sonnet | 10 credits |
| Claude 3 Opus | 15 credits |
Cost Calculation
// Formula
cost = Math.ceil((inputTokens + outputTokens) / 1000) * model.costPer1kTokens
// Example: 500 input + 800 output = 1300 tokens
// With GPT-4o Mini (1 credit/1k): ceil(1300/1000) × 1 = 2 credits
Tier Allocations
Free Tier
- 100 credits/month
- Access to: GPT-4o Mini only
- Features: Basic chat
Pro Tier
- 2,500 credits/month
- Access to: GPT-4o Mini, GPT-4o, Claude 3.5 Sonnet
- Features: RAG, multi-model, tool use, priority support
Enterprise Tier
- 10,000 credits/month
- Access to: All models
- Features: All features + team management
API Flow
Pre-Check (Estimation)
Before calling the AI:
// Estimate cost based on input length
const estimatedCost = estimateCreditCost(
selectedModel,
estimatedInputTokens,
maxOutputTokens
);
// Return 402 if insufficient
if (profile.credits < estimatedCost) {
return new Response("Insufficient Credits", { status: 402 });
}
Post-Check (Actual)
After AI response completes:
// In onFinish callback
const actualCost = calculateCreditCost(selectedModel, {
promptTokens: usage.promptTokens,
completionTokens: usage.completionTokens,
});
// Deduct credits
await supabase
.from("profiles")
.update({ credits: profile.credits - actualCost })
.eq("id", user.id);
Configuration
Model Costs (src/config/ai-models.ts)
export const AI_MODELS = {
"gpt-4o-mini": {
provider: "openai",
costPer1kTokens: 1,
// ...
},
"gpt-4o": {
provider: "openai",
costPer1kTokens: 5,
// ...
},
};
Tier Credits (src/config/pricing.ts)
export const PRICING_TIERS = {
free: {
monthlyCredits: 100,
creditReset: "monthly",
// ...
},
pro: {
monthlyCredits: 2500,
creditReset: "monthly",
// ...
},
};
Credit Utilities
estimateCreditCost
Pre-flight estimation:
import { estimateCreditCost } from "@/lib/ai/credit-calculator";
const cost = estimateCreditCost("gpt-4o-mini", 500, 1000);
// Returns: 2 (for 1500 estimated tokens)
calculateCreditCost
Post-completion calculation:
import { calculateCreditCost } from "@/lib/ai/credit-calculator";
const cost = calculateCreditCost("gpt-4o-mini", {
promptTokens: 500,
completionTokens: 800,
});
// Returns: 2 (for 1300 actual tokens)
UI Components
Credit Widget
Displays in sidebar:
- Current credit balance
- Visual progress bar
- Color coding (green/yellow/red)
- Click to navigate to billing
Insufficient Credits Dialog
Shown when 402 error occurs:
- Upgrade prompt
- Link to billing page
Credit Reset
Credits reset based on tier configuration:
creditReset: "monthly" // Resets on billing cycle
creditReset: "never" // Credits accumulate (top-ups)
Top-Up System
One-time credit purchases:
- User clicks "Top Up" on billing page
- Redirects to Stripe checkout (one-time payment)
- Webhook adds credits to existing balance
// In webhook handler
if (type === "one-time") {
const creditsToAdd = 1000;
await supabase
.from("profiles")
.update({ credits: profile.credits + creditsToAdd })
.eq("id", userId);
}
Monitoring Usage
Usage Chart
The billing page shows a 30-day usage chart:
- Credits spent per day
- Uses Recharts for visualization
- Data from
messages.tokens_used
Database Query
SELECT
DATE(created_at) as date,
SUM(tokens_used) as total_tokens
FROM messages
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY date;