Back to Blog

API Monetization: Metered Billing, Usage-Based Pricing

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
13 min read
Updated 2026

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

Quick answer. API monetization charges customers by consumption rather than fixed seats, the model used by Twilio, Stripe, AWS, Cloudflare, and OpenAI. It requires accurate usage tracking, reliable billing (e.g. Stripe usage records), and solid API key management, with pricing structured per-request, per-unit, in volume tiers, or as included-plus-overage.

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

api - API Monetization: Metered Billing, Usage-Based Pricing

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

Partnering 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.


Related Reading

apimonetizationstripebillingsaas
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 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.

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.