Back to Blog

API Monetization: Metered Billing, Usage-Based Pricing, and Stripe Usage Records

Monetize your API with usage-based pricing — metered billing with Stripe, API key management, usage tracking with Redis, rate limiting by plan, invoicing, and t

Viprasol Tech Team
May 13, 2026
13 min read

API Monetization: Metered Billing, Usage-Based Pricing, and Stripe Usage Records

Usage-based pricing (UBP) — charging customers based on what they consume rather than a fixed seat count — is the fastest-growing pricing model in SaaS. Twilio, Stripe, AWS, Cloudflare, and OpenAI all use it. The appeal is alignment: customers pay for value received, and costs scale with their success.

Implementing it requires three things: accurate usage tracking, reliable billing infrastructure, and thoughtful API key management.


Pricing Model Options

ModelStructureBest For
Per-request$0.001 per API callHigh-volume, low-margin APIs
Per-unit$0.05 per email sent, $0.10 per verificationTransactional services
Volume tiers$0.01/call (0–10K), $0.007/call (10K–100K)Incentivize high volume
Included + overage10,000 requests/month included; $0.002/call afterHybrid plans
Credit bundles$10 = 1,000 credits; 1 API call = N creditsComplex services with different costs
Monthly cap + UBP$99/month + $0.001/call above 100KEnterprise predictability

API Key Management

Every API consumer needs an API key. Keys serve three purposes: authentication (who are you?), authorization (what can you do?), and metering (track usage per customer).

// lib/apiKeys.ts
import crypto from 'crypto';
import { db } from './db';
import { redis } from './redis';

interface ApiKey {
  id: string;
  key: string;            // Never stored — only the hash
  keyHash: string;        // SHA-256 hash of the key
  keyPrefix: string;      // First 8 chars (for display: "sk_live_abc12...")
  tenantId: string;
  name: string;           // User-defined label ("Production", "Staging")
  plan: string;
  scopes: string[];       // ['read', 'write'] etc.
  rateLimitPerMinute: number;
  rateLimitPerDay: number;
  expiresAt?: Date;
  lastUsedAt?: Date;
  createdAt: Date;
}

export async function createApiKey(params: {
  tenantId: string;
  name: string;
  plan: string;
  scopes: string[];
}): Promise<{ apiKey: ApiKey; plainTextKey: string }> {
  // Generate cryptographically secure random key
  const randomBytes = crypto.randomBytes(32).toString('base64url');
  const plainTextKey = `sk_live_${randomBytes}`;

  // Store only the hash — never the plain text key
  const keyHash = crypto.createHash('sha256').update(plainTextKey).digest('hex');
  const keyPrefix = plainTextKey.substring(0, 12);

  const rateLimits = getPlanLimits(params.plan);

  const apiKey = await db.apiKeys.create({
    keyHash,
    keyPrefix,
    tenantId: params.tenantId,
    name: params.name,
    plan: params.plan,
    scopes: params.scopes,
    rateLimitPerMinute: rateLimits.perMinute,
    rateLimitPerDay: rateLimits.perDay,
  });

  return { apiKey, plainTextKey };  // plainTextKey shown once to user
}

export async function validateApiKey(
  rawKey: string
): Promise<ApiKey | null> {
  if (!rawKey.startsWith('sk_live_') && !rawKey.startsWith('sk_test_')) {
    return null;
  }

  const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');

  // Cache in Redis (avoid DB hit on every request)
  const cacheKey = `apikey:${keyHash}`;
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  const apiKey = await db.apiKeys.findByHash(keyHash);
  if (!apiKey) return null;
  if (apiKey.expiresAt && apiKey.expiresAt < new Date()) return null;

  // Cache for 5 minutes
  await redis.setex(cacheKey, 300, JSON.stringify(apiKey));

  return apiKey;
}

🚀 SaaS MVP in 8 Weeks — Seriously

We have launched 50+ SaaS platforms. Multi-tenant architecture, Stripe billing, auth, role-based access, and cloud deployment — all handled by one senior team.

  • Week 1–2: Architecture design + wireframes
  • Week 3–6: Core features built + tested
  • Week 7–8: Launch-ready on AWS/Vercel with CI/CD
  • Post-launch: Maintenance plans from month 3

Usage Tracking

Every billable API call must be tracked accurately and atomically:

// lib/usageTracker.ts
import { redis } from './redis';
import { db } from './db';

interface UsageEvent {
  tenantId: string;
  apiKeyId: string;
  endpoint: string;
  units: number;          // e.g., 1 for a request, or token count for AI
  billingMetric: string;  // e.g., 'api_requests', 'tokens_processed'
  metadata?: Record<string, unknown>;
}

// Buffer usage events in Redis; flush to DB periodically
export async function trackUsage(event: UsageEvent): Promise<void> {
  const minute = Math.floor(Date.now() / 60000);
  const day = new Date().toISOString().split('T')[0];

  const pipeline = redis.pipeline();

  // Increment counters (for rate limiting and real-time display)
  pipeline.incrby(`usage:minute:${event.tenantId}:${minute}`, event.units);
  pipeline.expire(`usage:minute:${event.tenantId}:${minute}`, 120);
  pipeline.incrby(`usage:day:${event.tenantId}:${day}`, event.units);
  pipeline.expire(`usage:day:${event.tenantId}:${day}`, 86400 * 2);

  // Buffer event for Stripe reporting (flush every minute via cron)
  pipeline.rpush('usage:pending', JSON.stringify({
    ...event,
    timestamp: Date.now(),
  }));

  await pipeline.exec();
}

// Cron job: flush buffered usage to Stripe and DB
export async function flushUsageToStripe(): Promise<void> {
  const pending = await redis.lrange('usage:pending', 0, -1);
  if (pending.length === 0) return;

  // Remove from buffer (pipeline to minimize race conditions)
  await redis.ltrim('usage:pending', pending.length, -1);

  const events: UsageEvent[] = pending.map(e => JSON.parse(e));

  // Group by tenant for batch Stripe reporting
  const byTenant = events.reduce<Record<string, UsageEvent[]>>((acc, e) => {
    (acc[e.tenantId] ??= []).push(e);
    return acc;
  }, {});

  for (const [tenantId, tenantEvents] of Object.entries(byTenant)) {
    const totalUnits = tenantEvents.reduce((sum, e) => sum + e.units, 0);
    const subscription = await db.subscriptions.findByTenantId(tenantId);

    if (subscription?.stripeSubscriptionItemId) {
      await stripe.subscriptionItems.createUsageRecord(
        subscription.stripeSubscriptionItemId,
        {
          quantity: totalUnits,
          timestamp: Math.floor(Date.now() / 1000),
          action: 'increment',  // Add to existing usage, don't set absolute
        }
      );
    }

    // Also save to DB for internal analytics
    await db.usageEvents.createMany(tenantEvents);
  }
}

Stripe Metered Billing Setup

// Setting up metered billing in Stripe
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// Create a metered price (one-time setup via Stripe Dashboard or API)
const meteredPrice = await stripe.prices.create({
  currency: 'usd',
  recurring: {
    interval: 'month',
    usage_type: 'metered',   // Charge based on reported usage
    aggregate_usage: 'sum',  // Sum all usage records in the period
  },
  unit_amount: 1,            // $0.001 per unit (in cents × 0.001 = fractions)
  // For sub-cent pricing, use tiers instead
  billing_scheme: 'tiered',
  tiers_mode: 'graduated',
  tiers: [
    { up_to: 10000, unit_amount: 0, flat_amount: 0 },  // First 10K free
    { up_to: 100000, unit_amount_decimal: '0.1' },      // $0.001 per unit
    { up_to: 'inf', unit_amount_decimal: '0.07' },      // $0.0007 per unit (volume discount)
  ],
  product: 'prod_xxxxx',  // Your API product
});

// When customer subscribes, create subscription with metered item
async function createSubscription(tenantId: string, stripeCustomerId: string) {
  const subscription = await stripe.subscriptions.create({
    customer: stripeCustomerId,
    items: [
      {
        price: 'price_base_monthly',  // Fixed base fee ($99/month)
      },
      {
        price: meteredPrice.id,  // Metered usage component
      },
    ],
    billing_cycle_anchor: 'now',
  });

  // Store the subscription item ID for the metered component
  const meteredItem = subscription.items.data.find(
    item => item.price.id === meteredPrice.id
  );

  await db.subscriptions.update(tenantId, {
    stripeSubscriptionId: subscription.id,
    stripeSubscriptionItemId: meteredItem!.id,
  });
}

💡 The Difference Between a SaaS Demo and a SaaS Business

Anyone can build a demo. We build SaaS products that handle real load, real users, and real payments — with architecture that does not need to be rewritten at 1,000 users.

  • Multi-tenant PostgreSQL with row-level security
  • Stripe subscriptions, usage billing, annual plans
  • SOC2-ready infrastructure from day one
  • We own zero equity — you own everything

Usage Dashboard for Customers

Show customers their usage in real-time — reduces support tickets and bill shock:

// api/usage.ts
app.get('/api/usage/current-period', async (request, reply) => {
  const tenantId = request.tenantId;
  const today = new Date().toISOString().split('T')[0];

  // Real-time from Redis (today's usage)
  const todayUsage = await redis.get(`usage:day:${tenantId}:${today}`);

  // Historical from DB (this billing period)
  const periodStart = getPeriodStart(request.user.billingCycleAnchor);
  const periodUsage = await db.query(
    `SELECT
       billing_metric,
       SUM(units) AS total_units,
       DATE(created_at) AS date
     FROM usage_events
     WHERE tenant_id = $1 AND created_at >= $2
     GROUP BY billing_metric, DATE(created_at)
     ORDER BY date DESC`,
    [tenantId, periodStart]
  );

  // Estimated bill this period
  const totalUnits = periodUsage.rows.reduce((sum, r) => sum + parseInt(r.total_units), 0);
  const estimatedCost = calculateEstimatedCost(totalUnits, request.user.plan);

  return reply.send({
    currentPeriod: {
      start: periodStart,
      totalUnits,
      estimatedCostCents: estimatedCost,
      dailyBreakdown: periodUsage.rows,
    },
    planLimits: getPlanLimits(request.user.plan),
    todayUnits: parseInt(todayUsage ?? '0'),
  });
});

Billing Webhooks and Dunning

// Handle failed payments for metered subscriptions
async function handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
  const tenantId = await getTenantByStripeCustomer(invoice.customer as string);

  // Grace period: don't immediately suspend on first failure
  const failureCount = await incrementPaymentFailures(tenantId);

  if (failureCount === 1) {
    // Notify customer, allow continued access
    await sendPaymentFailedEmail(tenantId, { retryDate: getRetryDate(invoice) });
  } else if (failureCount === 3) {
    // Suspend API access after 3 failed attempts
    await suspendTenantAccess(tenantId);
    await sendSuspensionEmail(tenantId);
  }
}

Working With Viprasol

We build API monetization infrastructure — Stripe metered billing integration, API key management systems, usage tracking pipelines, customer-facing dashboards, and dunning workflows. Usage-based pricing done correctly aligns your revenue with your customers' success.

Talk to our team about API monetization implementation.


See Also

Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Building a SaaS Product?

We've helped launch 50+ SaaS platforms. Let's build yours — fast.

Free consultation • No commitment • Response within 24 hours

Viprasol · AI Agent Systems

Add AI automation to your SaaS product?

Viprasol builds custom AI agent crews that plug into any SaaS workflow — automating repetitive tasks, qualifying leads, and responding across every channel your customers use.