Building AI-Powered Apps with Credit Metering
How to implement per-request credit billing for AI features like image generation, chat completions, and embeddings.
By cred.diy Team

AI APIs are expensive, and usage varies wildly between users. A user generating 10 images costs 100x more than one who generates a single chat response. Credit metering lets you align costs with usage, making AI features sustainable.
Why Credits for AI?
Traditional subscription models fail for AI because:
- Cost unpredictability: One power user can consume your entire margin
- Unfair pricing: Light users subsidize heavy users
- Scaling risk: More users = more API costs with fixed revenue
Credits solve this by creating a direct link between consumption and revenue.
Credit Cost Architecture
Define Your Cost Model
Map each AI operation to a credit cost:
| Operation | API Cost | Credit Cost | Margin |
|---|---|---|---|
| Chat message (GPT-4) | ~$0.03 | 3 credits | 50% |
| Image generation (DALL-E) | ~$0.04 | 5 credits | 60% |
| Text embedding | ~$0.0001 | 1 credit | 90% |
| Code completion | ~$0.02 | 2 credits | 50% |
| Voice transcription (1 min) | ~$0.006 | 1 credit | 40% |
Set Up Credit Types
// Create an AI-specific credit type
const aiCredits = {
name: "AI Credits",
symbol: "AIC",
description: "Credits for AI-powered features",
exchange_rate: 0.01 // 1 credit = $0.01
};
Implementation Pattern
The Check-Deduct-Execute Pattern
Always follow this order to prevent unpaid usage:
const API = 'https://eutdgemlrpvnxfkkpvlu.supabase.co/functions/v1/credits-api';
const headers = { 'Content-Type': 'application/json', 'x-api-key': API_KEY };
const generateImage = async (userEmail, prompt) => {
const COST = 5; // credits per generation
const requestId = crypto.randomUUID();
// Step 1: Check balance (one entry per credit type)
const balanceRes = await fetch(
`${API}/balance?email=${encodeURIComponent(userEmail)}&credit_type_id=ai_credits_id`,
{ headers }
);
const { balances } = await balanceRes.json();
const balance = balances[0]?.balance ?? 0;
if (balance < COST) {
throw new Error(`Insufficient credits. Need ${COST}, have ${balance}`);
}
// Step 2: Deduct credits BEFORE calling AI
// (also returns 400 INSUFFICIENT_CREDITS if the balance changed meanwhile)
await fetch(`${API}/deduct`, {
method: 'POST',
headers: { ...headers, 'Idempotency-Key': requestId },
body: JSON.stringify({
user_email: userEmail,
credit_type_id: 'ai_credits_id',
amount: COST,
reason: 'Image generation',
reference_id: requestId
})
});
// Step 3: Execute the AI call
try {
const image = await callDallE(prompt);
return image;
} catch (error) {
// Step 4: Reverse the deduction on failure
await fetch(`${API}/refund`, {
method: 'POST',
headers,
body: JSON.stringify({
reference_id: requestId,
reason: 'Refund: generation failed'
})
});
throw error;
}
};;
Token-Based Metering for Chat
For chat completions where cost scales with tokens:
const handleChatMessage = async (userEmail, messages) => {
// Estimate token cost upfront
const estimatedTokens = estimateTokenCount(messages);
const estimatedCost = Math.ceil(estimatedTokens / 1000); // 1 credit per 1K tokens
// Check sufficient balance
const { balance } = await getBalance(userEmail);
if (balance < estimatedCost) {
throw new Error('Insufficient credits for this message');
}
// Call AI and get actual usage
const response = await callChatAPI(messages);
const actualTokens = response.usage.total_tokens;
const actualCost = Math.ceil(actualTokens / 1000);
// Deduct actual cost
await deductCredits(userEmail, actualCost, 'Chat completion', {
tokens_used: actualTokens,
model: 'gpt-4'
});
return response;
};
Pricing Tiers for AI Apps
Structure tiers around typical usage patterns:
const aiPricingTiers = [
{
name: "Explorer",
credits: 100,
price: 4.99,
description: "~20 image generations or 100 chat messages"
},
{
name: "Creator",
credits: 500,
price: 19.99,
description: "~100 image generations or 500 chat messages"
},
{
name: "Professional",
credits: 2000,
price: 59.99,
description: "~400 image generations or 2000 chat messages"
}
];
Showing Users Their Usage
Build transparency into your UI:
// Display credit cost before action
const CreditCostBadge = ({ cost, action }) => (
<div className="flex items-center gap-2">
<span className="text-sm text-muted">
This will use {cost} credit{cost > 1 ? 's' : ''}
</span>
<button onClick={action}>Generate</button>
</div>
);
// Show remaining balance after action
const BalanceIndicator = ({ balance }) => (
<div className={balance < 10 ? 'text-red-500' : 'text-green-500'}>
{balance} credits remaining
{balance < 10 && (
<a href="/buy-credits">Top up →</a>
)}
</div>
);
Handling Edge Cases
Rate Limiting with Credits
Credits naturally rate-limit users – when they run out, they stop. But add safeguards:
// Prevent rapid-fire deductions
const RATE_LIMIT = 10; // max requests per minute
const rateLimiter = new Map();
const checkRateLimit = (userId) => {
const now = Date.now();
const userRequests = rateLimiter.get(userId) || [];
const recentRequests = userRequests.filter(t => now - t < 60000);
if (recentRequests.length >= RATE_LIMIT) {
throw new Error('Rate limit exceeded. Please wait.');
}
recentRequests.push(now);
rateLimiter.set(userId, recentRequests);
};
Webhook Notifications
Alert users before they run out:
// Set up low-balance webhook
app.post('/webhooks/cred', (req, res) => {
if (req.headers['x-cred-event'] === 'credits.low') {
const { user_email, balance } = req.body;
sendEmail(user_email, {
subject: 'Your AI credits are running low',
body: `You have ${balance} credits remaining. Top up to keep creating!`
});
}
res.status(200).send('OK');
});
Best Practices
- Deduct before execution – never let unpaid AI calls through
- Refund on failure – always issue credits back if the AI call fails
- Show costs upfront – display credit cost before each action
- Log everything – use transaction metadata for cost analytics
- Set minimum balances – require at least 1 operation's worth of credits
- Offer bundles – higher tiers should have better per-credit pricing
Conclusion
Credit metering transforms AI features from a cost center into a sustainable revenue stream. By charging per-operation, you align your revenue with your costs and give users the flexibility to pay for exactly what they use.
Start metering your AI features with cred.diy.

