Blog
    Tutorial9 min read

    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

    Building AI-Powered Apps with Credit Metering

    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:

    OperationAPI CostCredit CostMargin
    Chat message (GPT-4)~$0.033 credits50%
    Image generation (DALL-E)~$0.045 credits60%
    Text embedding~$0.00011 credit90%
    Code completion~$0.022 credits50%
    Voice transcription (1 min)~$0.0061 credit40%

    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

    1. Deduct before execution – never let unpaid AI calls through
    2. Refund on failure – always issue credits back if the AI call fails
    3. Show costs upfront – display credit cost before each action
    4. Log everything – use transaction metadata for cost analytics
    5. Set minimum balances – require at least 1 operation's worth of credits
    6. 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.

    Try it on a real project.