Many tutorials suggest adding a remaining_credits integer column to your Supabase users table for AI rate limiting. This approach has problems:
remaining_credits column.https://cap-alpha-one.vercel.app/v1/consume
Cap provides a hosted consume() gate that handles atomic operations, daily limits, idempotency, and HTTP 402 responses:
// Before calling your AI route
const response = await fetch('https://cap-alpha-one.vercel.app/v1/consume', {
method: 'POST',
headers: {
'Authorization': 'Bearer cap_...',
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId: req.user.id,
units: 1,
idempotencyKey: req.headers['x-request-id'] // optional
})
});
const gate = await response.json();
if (!gate.ok) {
// HTTP 402: User exceeded daily limit
// { ok: false, reason: "insufficient_balance", remaining: 0 }
return res.status(402).json({
error: 'Daily limit exceeded',
remaining: gate.remaining
});
}
// HTTP 200: Allowed - proceed with AI route
// { ok: true, remaining: 19 }
const completion = await openai.chat.completions.create({...});
res.json(completion);
idempotencyKeyapp.post('/api/ai/chat', async (req, res) => {
// Gate the route with Cap
const capResponse = await fetch('https://cap-alpha-one.vercel.app/v1/consume', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CAP_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId: req.user.id,
units: 1,
idempotencyKey: req.headers['x-request-id']
})
});
const gate = await capResponse.json();
if (!gate.ok) {
// User exceeded daily limit
return res.status(402).json({
error: 'Daily limit exceeded',
reason: gate.reason,
remaining: gate.remaining
});
}
// Allowed - proceed with AI call
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: req.body.messages
});
res.json(response);
});
| Feature | DIY Supabase Credits | Cap Hosted API |
|---|---|---|
| Setup | Add column, migration, index | Mint key, 1 API call |
| Race conditions | Need row locking | Atomic by default |
| Daily reset | Build cron job | Automatic |
| Idempotency | Build yourself | Built-in |
| Maintenance | Ongoing debugging | None |
Mint a sandbox API key (no signup required):
curl -X POST https://cap-alpha-one.vercel.app/v1/mint_sandbox_key
# Returns: { "projectId": "...", "apiKey": "cap_..." }
Then replace your Supabase credits column with one consume() call to https://cap-alpha-one.vercel.app/v1/consume.