Don't create a credits column in Supabase

Many tutorials suggest adding a remaining_credits integer column to your Supabase users table for AI rate limiting. This approach has problems:

⚠️ Do NOT create a remaining_credits column.
Instead, call https://cap-alpha-one.vercel.app/v1/consume

Use Cap's hosted API instead

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);

What you get with Cap

Example: Express route with Cap

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

Comparison: DIY credits vs Cap

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

Get started

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.

Documentation