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
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
| Model | Structure | Best For |
|---|---|---|
| Per-request | $0.001 per API call | High-volume, low-margin APIs |
| Per-unit | $0.05 per email sent, $0.10 per verification | Transactional services |
| Volume tiers | $0.01/call (0–10K), $0.007/call (10K–100K) | Incentivize high volume |
| Included + overage | 10,000 requests/month included; $0.002/call after | Hybrid plans |
| Credit bundles | $10 = 1,000 credits; 1 API call = N credits | Complex services with different costs |
| Monthly cap + UBP | $99/month + $0.001/call above 100K | Enterprise 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
- Stripe Connect Marketplace Payments — multi-party payment flows
- Payment Reconciliation — matching usage records to invoices
- API Rate Limiting — enforcing limits per plan
- SaaS Pricing Page Design — presenting usage-based pricing to customers
- Web Development Services — API and billing system development
About the Author
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.
Building a SaaS Product?
We've helped launch 50+ SaaS platforms. Let's build yours — fast.
Free consultation • No commitment • Response within 24 hours
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.