Building Usage-Based Billing for SaaS APIs: From Rate Limits to Revenue
Transform your SaaS API from cost center to profit center. Learn how to implement usage-based billing with rate limiting, metering, and Stripe integration using Rately.
Business Expert
• 12 min read
Building Usage-Based Billing for SaaS APIs
Usage-based billing is transforming how SaaS companies monetize their APIs. Instead of flat monthly fees, charge customers based on actual API consumption. This model aligns costs with value, reduces barriers to adoption, and can significantly increase revenue.
Here's how to implement usage-based billing that scales from startup to enterprise using Rately's rate limiting and metering platform.
Why Usage-Based Billing for APIs?
Traditional SaaS pricing challenges:
- Flat fees don't match customer value
- Hard to predict costs for customers
- Heavy users subsidized by light users
- Difficult to upsell based on usage
Usage-based billing benefits:
- Revenue grows with customer success
- Lower barrier to entry for new customers
- Fair pricing that matches consumption
- Automatic expansion revenue as usage grows
The Complete Architecture
Here's the full stack for usage-based API billing:
Customer API Request
↓
Rately Rate Limiting & Metering
↓
Your API Service
↓
Usage Data → Stripe Billing
↓
Customer Invoice
Step 1: Design Your Pricing Model
Before implementing, design your pricing tiers and usage metrics:
Example: AI API Service
{
"pricing_model": {
"base_plans": [
{
"name": "starter",
"monthly_base": 0,
"included_requests": 1000,
"overage_per_request": 0.01
},
{
"name": "pro",
"monthly_base": 49,
"included_requests": 50000,
"overage_per_request": 0.005
},
{
"name": "enterprise",
"monthly_base": 199,
"included_requests": 500000,
"overage_per_request": 0.002
}
],
"endpoint_multipliers": {
"/ai/text": 1.0,
"/ai/image": 5.0,
"/ai/video": 25.0
}
}
}
Step 2: Implement Rate Limiting with Usage Tracking
Configure Rately to enforce limits AND track usage:
{
"policies": [
{
"name": "Starter Plan Limits",
"match": "/api/*",
"limit": 1000,
"window": "1mo",
"key_template": "user:{jwt.sub}",
"billing": {
"meter": true,
"plan": "starter",
"overage_allowed": true
},
"conditions": {
"jwt.plan": "starter"
}
},
{
"name": "Pro Plan Limits",
"match": "/api/*",
"limit": 50000,
"window": "1mo",
"key_template": "user:{jwt.sub}",
"billing": {
"meter": true,
"plan": "pro",
"overage_allowed": true
},
"conditions": {
"jwt.plan": "pro"
}
}
]
}
Step 3: Advanced Metering Configuration
Track different types of usage with custom weights:
{
"metering_rules": [
{
"name": "Text API Calls",
"match": "/api/ai/text",
"weight": 1,
"unit": "request"
},
{
"name": "Image API Calls",
"match": "/api/ai/image",
"weight": 5,
"unit": "request"
},
{
"name": "Compute-Heavy Operations",
"match": "/api/ai/video",
"weight": 25,
"unit": "request"
}
]
}
Step 4: Stripe Integration
Connect Rately's usage data to Stripe for automatic billing:
Create Stripe Products and Prices
// Create base subscription product
const baseProduct = await stripe.products.create({
name: 'API Access - Pro Plan',
description: 'Pro plan with 50k included requests'
});
const basePrice = await stripe.prices.create({
product: baseProduct.id,
unit_amount: 4900, // $49.00
currency: 'usd',
recurring: {
interval: 'month'
}
});
// Create usage-based pricing for overages
const usageProduct = await stripe.products.create({
name: 'API Request Overage',
description: 'Additional API requests beyond plan limits'
});
const usagePrice = await stripe.prices.create({
product: usageProduct.id,
unit_amount: 50, // $0.50 per 100 requests
currency: 'usd',
recurring: {
interval: 'month',
usage_type: 'metered'
},
billing_scheme: 'tiered',
tiers: [
{
up_to: 10000,
unit_amount: 50 // $0.005 per request for first 10k overage
},
{
up_to: 'inf',
unit_amount: 25 // $0.0025 per request for additional
}
]
});
Sync Usage Data Daily
// Daily job to sync usage from Rately to Stripe
async function syncUsageToStripe() {
// Get yesterday's usage from Rately
const usage = await rately.getUsage({
start_date: yesterday(),
end_date: today(),
group_by: ['user_id', 'plan']
});
for (const record of usage) {
const customer = await getCustomerByUserId(record.user_id);
const subscription = await stripe.subscriptions.retrieve(customer.subscription_id);
// Find the metered usage item
const usageItem = subscription.items.data.find(
item => item.price.id === usagePrice.id
);
if (usageItem && record.requests > record.included_requests) {
const overageRequests = record.requests - record.included_requests;
// Report usage to Stripe
await stripe.subscriptionItems.createUsageRecord(usageItem.id, {
quantity: Math.ceil(overageRequests / 100), // Bill in units of 100
timestamp: Math.floor(Date.now() / 1000)
});
}
}
}
Step 5: Customer Dashboard Integration
Show real-time usage to customers:
// API endpoint for customer usage dashboard
app.get('/api/usage', authenticateUser, async (req, res) => {
const usage = await rately.getUserUsage({
user_id: req.user.id,
period: 'current_month'
});
const plan = await getUserPlan(req.user.id);
res.json({
current_usage: usage.requests,
included_requests: plan.included_requests,
overage_requests: Math.max(0, usage.requests - plan.included_requests),
estimated_bill: calculateEstimatedBill(usage, plan),
usage_by_endpoint: usage.breakdown,
reset_date: usage.reset_date
});
});
function calculateEstimatedBill(usage, plan) {
const overage = Math.max(0, usage.requests - plan.included_requests);
return plan.monthly_base + (overage * plan.overage_per_request);
}
React Component for Usage Display
function UsageDashboard() {
const [usage, setUsage] = useState(null);
useEffect(() => {
fetch('/api/usage').then(r => r.json()).then(setUsage);
}, []);
if (!usage) return <div>Loading...</div>;
const usagePercent = (usage.current_usage / usage.included_requests) * 100;
const isNearLimit = usagePercent > 80;
return (
<div className="usage-dashboard">
<h3>API Usage This Month</h3>
<div className="usage-meter">
<div className="meter-bar">
<div
className={`meter-fill ${isNearLimit ? 'warning' : 'normal'}`}
style={{ width: `${Math.min(usagePercent, 100)}%` }}
/>
</div>
<span>{usage.current_usage.toLocaleString()} / {usage.included_requests.toLocaleString()}</span>
</div>
{usage.overage_requests > 0 && (
<div className="overage-alert">
<strong>Overage:</strong> {usage.overage_requests.toLocaleString()} requests
<span>Additional charges: ${(usage.overage_requests * 0.005).toFixed(2)}</span>
</div>
)}
<div className="estimated-bill">
<strong>Estimated Bill: ${usage.estimated_bill.toFixed(2)}</strong>
</div>
<div className="usage-breakdown">
<h4>Usage by Endpoint</h4>
{Object.entries(usage.usage_by_endpoint).map(([endpoint, count]) => (
<div key={endpoint} className="endpoint-usage">
<span>{endpoint}</span>
<span>{count.toLocaleString()} requests</span>
</div>
))}
</div>
</div>
);
}
Step 6: Smart Limit Management
Automatically adjust limits based on billing status:
{
"dynamic_limits": {
"base_limit_from_plan": true,
"overage_behavior": "soft_limit",
"overage_limit_multiplier": 2.0,
"hard_stop_at_amount": 1000.00,
"notifications": {
"80_percent": "email",
"100_percent": "email_and_webhook",
"overage_start": "email_and_webhook"
}
}
}
Step 7: Analytics and Optimization
Track key metrics to optimize your billing:
-- Key Usage-Based Billing Metrics
-- Average Revenue Per User (ARPU) by plan
SELECT
plan,
AVG(monthly_revenue) as arpu,
AVG(monthly_requests) as avg_usage
FROM user_billing_summary
WHERE month = CURRENT_MONTH
GROUP BY plan;
-- Usage distribution (identify heavy users)
SELECT
usage_bucket,
COUNT(*) as users,
AVG(monthly_revenue) as avg_revenue
FROM (
SELECT
user_id,
monthly_requests,
monthly_revenue,
CASE
WHEN monthly_requests < 1000 THEN 'light'
WHEN monthly_requests < 10000 THEN 'medium'
WHEN monthly_requests < 50000 THEN 'heavy'
ELSE 'enterprise'
END as usage_bucket
FROM user_billing_summary
WHERE month = CURRENT_MONTH
) t
GROUP BY usage_bucket;
-- Revenue expansion opportunities
SELECT
user_id,
current_plan,
monthly_requests,
overage_amount,
CASE
WHEN current_plan = 'starter' AND monthly_requests > 5000 THEN 'upsell_to_pro'
WHEN current_plan = 'pro' AND monthly_requests > 40000 THEN 'upsell_to_enterprise'
ELSE 'no_action'
END as recommendation
FROM user_billing_summary
WHERE overage_amount > 20;
Step 8: Customer Communication
Proactive communication prevents billing surprises:
Usage Alerts
// Send usage alerts
async function sendUsageAlerts() {
const highUsageUsers = await rately.getUsersNearLimit({
threshold: 0.8,
period: 'current_month'
});
for (const user of highUsageUsers) {
await sendEmail({
to: user.email,
subject: "You're approaching your API limit",
template: 'usage_warning',
data: {
current_usage: user.usage.requests,
limit: user.plan.included_requests,
percentage: Math.round((user.usage.requests / user.plan.included_requests) * 100),
upgrade_url: `https://app.yourservice.com/billing/upgrade`
}
});
}
}
Monthly Usage Reports
// Monthly usage summary
async function sendMonthlyUsageReport(userId) {
const usage = await rately.getUserUsage({
user_id: userId,
period: 'last_month'
});
const bill = await stripe.invoices.upcoming({
customer: user.stripe_customer_id
});
await sendEmail({
to: user.email,
subject: "Your API usage summary",
template: 'monthly_usage',
data: {
usage: usage.requests.toLocaleString(),
top_endpoints: usage.top_endpoints,
total_bill: bill.total / 100,
next_bill_date: new Date(bill.period_end * 1000).toLocaleDateString()
}
});
}
Common Pitfalls and Solutions
1. Usage Tracking Gaps
Problem: Missing usage data due to network issues or failures Solution: Implement retry logic and usage reconciliation
// Reconcile usage data weekly
async function reconcileUsage() {
const ratelyUsage = await rately.getUsage({ period: 'last_week' });
const stripeUsage = await getStripeUsage({ period: 'last_week' });
const discrepancies = findDiscrepancies(ratelyUsage, stripeUsage);
for (const discrepancy of discrepancies) {
await stripe.subscriptionItems.createUsageRecord(discrepancy.item_id, {
quantity: discrepancy.missing_quantity,
timestamp: discrepancy.timestamp,
action: 'increment'
});
}
}
2. Rate Limit vs Billing Mismatch
Problem: Users hit rate limits but haven't hit billing limits Solution: Align rate limiting with billing periods
{
"policy": {
"limit_type": "billing_aligned",
"reset_with_billing_cycle": true,
"soft_limit_at": 0.9,
"hard_limit_at": 2.0
}
}
3. Surprise Bills
Problem: Customers surprised by overage charges Solution: Progressive notifications and spending caps
{
"notifications": [
{ "at_percent": 50, "message": "Halfway through your monthly API allowance" },
{ "at_percent": 80, "message": "80% of allowance used - consider upgrading" },
{ "at_percent": 100, "message": "Allowance exceeded - overage charges apply" },
{ "at_amount": 100, "message": "Spending cap reached - API access paused" }
]
}
Success Metrics to Track
- Monthly Recurring Revenue (MRR) from usage charges
- Usage expansion rate (month-over-month growth)
- Customer satisfaction with billing transparency
- Churn rate by usage level
- Average time to upgrade based on usage patterns
Conclusion
Usage-based billing transforms your API from a cost center into a revenue driver. By combining Rately's rate limiting and metering with Stripe's billing infrastructure, you can:
- Align pricing with value - customers pay for what they use
- Reduce barriers to adoption - low or no upfront costs
- Scale revenue automatically - bill grows with customer success
- Gain usage insights - understand how customers use your API
Start simple with basic overage billing, then evolve to sophisticated tiered pricing as your API business grows.
Ready to implement usage-based billing? Get started with Rately and see how easy it is to monetize your API usage.