Lago vs Orb vs Metronome: Usage-Based Billing APIs Compared (2026)
TL;DR: Lago is the open-source billing engine you self-host — event ingestion, flexible pricing models, and full invoice control with no per-transaction fees. Orb is the developer-first cloud billing platform — SQL-based metrics, real-time usage tracking, and first-class support for complex pricing tiers. Metronome handles billing for high-scale infrastructure companies — pre-aggregated metering, commits and drawdowns, and enterprise contract management. In 2026: Lago if you want open-source and full control, Orb if you want the best developer experience for usage-based pricing, Metronome if you're selling infrastructure with complex enterprise contracts.
Key Takeaways
- Lago: Open-source (AGPLv3), self-hosted or cloud. Event-based metering, percentage/graduated/package/volume pricing. Ruby/Rails backend, GraphQL + REST APIs. Best for teams wanting full billing control without vendor lock-in
- Orb: Cloud-only, developer-first. SQL-based metric definitions, real-time usage dashboards, automatic invoice generation. Best for SaaS with complex usage-based pricing tiers
- Metronome: Cloud-only, enterprise-focused. Pre-aggregated metering at scale, commits/credits/drawdowns, contract management. Best for infrastructure companies with high event volumes and enterprise sales motions
Lago — Open-Source Billing Engine
Lago gives you a self-hosted billing engine with event ingestion, flexible pricing, and invoice generation — all without per-transaction fees.
Ingesting Usage Events
// lago-node SDK — send usage events
import { Client } from "lago-javascript-client";
const lago = Client({
apiKey: process.env.LAGO_API_KEY!,
apiUrl: "https://billing.yourcompany.com", // self-hosted
});
// Ingest a usage event
await lago.events.createEvent({
event: {
transaction_id: crypto.randomUUID(), // idempotency key
external_subscription_id: "sub_12345",
code: "api_calls", // matches billable metric code
timestamp: Math.floor(Date.now() / 1000),
properties: {
tokens: 1500,
model: "gpt-4",
},
},
});
// Batch ingest for high throughput
await lago.events.createBatchEvents({
events: usageRecords.map((record) => ({
transaction_id: record.id,
external_subscription_id: record.subscriptionId,
code: "api_calls",
timestamp: record.timestamp,
properties: { tokens: record.tokens },
})),
});
Defining Billable Metrics
// Create a billable metric — sum of tokens used
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "API Token Usage",
code: "api_calls",
aggregation_type: "sum_agg",
field_name: "tokens",
recurring: false,
},
});
// Weighted sum — track compute hours
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "Compute Hours",
code: "compute",
aggregation_type: "weighted_sum_agg",
field_name: "cpu_seconds",
weighted_interval: "seconds",
},
});
// Unique count — active users per billing period
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "Active Users",
code: "active_users",
aggregation_type: "unique_count_agg",
field_name: "user_id",
},
});
Pricing Plans with Graduated Tiers
// Create a plan with graduated pricing
await lago.plans.createPlan({
plan: {
name: "Growth Plan",
code: "growth",
interval: "monthly",
amount_cents: 4900, // $49 base price
amount_currency: "USD",
pay_in_advance: true,
charges: [
{
billable_metric_id: apiMetricId,
charge_model: "graduated",
properties: {},
graduated_ranges: [
{ from_value: 0, to_value: 10000, per_unit_amount: "0.00", flat_amount: "0" },
{ from_value: 10001, to_value: 100000, per_unit_amount: "0.002", flat_amount: "0" },
{ from_value: 100001, to_value: null, per_unit_amount: "0.001", flat_amount: "0" },
],
},
{
billable_metric_id: computeMetricId,
charge_model: "percentage",
properties: {
rate: "2.5",
fixed_amount: "0.10", // minimum charge per transaction
},
},
],
},
});
Subscriptions and Invoices
// Assign a subscription
await lago.subscriptions.createSubscription({
subscription: {
external_customer_id: "cust_42",
plan_code: "growth",
external_id: "sub_12345",
billing_time: "calendar", // or "anniversary"
},
});
// Retrieve current usage for a customer
const usage = await lago.customers.findCustomerCurrentUsage(
"cust_42",
{ external_subscription_id: "sub_12345" }
);
console.log(usage.data.charges_usage);
// [{ billable_metric: { code: "api_calls" }, units: "45230", amount_cents: 7046 }]
// List invoices
const invoices = await lago.invoices.findAllInvoices({
external_customer_id: "cust_42",
status: "finalized",
});
// Download invoice PDF
const pdf = await lago.invoices.downloadInvoice(invoices.data[0].lago_id);
Webhooks for Payment Integration
import express from "express";
import crypto from "crypto";
const app = express();
app.post("/webhooks/lago", express.raw({ type: "application/json" }), (req, res) => {
// Verify webhook signature
const signature = req.headers["x-lago-signature"];
const hmac = crypto.createHmac("sha256", process.env.LAGO_WEBHOOK_SECRET!);
hmac.update(req.body);
const expected = hmac.digest("hex");
if (signature !== expected) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body.toString());
switch (event.webhook_type) {
case "invoice.created":
// Trigger payment via Stripe/payment provider
processPayment(event.invoice);
break;
case "invoice.payment_status_updated":
// Update internal records
updatePaymentStatus(event.invoice);
break;
case "event.error":
// Handle ingestion errors
handleEventError(event);
break;
}
res.status(200).send("OK");
});
Orb — Developer-First Cloud Billing
Orb is a cloud billing platform with SQL-based metric definitions, real-time usage tracking, and automatic invoice generation.
Ingesting Events
import Orb from "orb-billing";
const orb = new Orb({ apiKey: process.env.ORB_API_KEY! });
// Ingest a single event
await orb.events.ingest({
events: [
{
idempotency_key: crypto.randomUUID(),
external_customer_id: "cust_42",
event_name: "api_call",
timestamp: new Date().toISOString(),
properties: {
tokens: 1500,
model: "gpt-4",
region: "us-east-1",
},
},
],
});
// High-throughput batch ingestion
const batch = usageRecords.map((r) => ({
idempotency_key: r.id,
external_customer_id: r.customerId,
event_name: "api_call",
timestamp: r.timestamp,
properties: { tokens: r.tokens, model: r.model },
}));
await orb.events.ingest({ events: batch });
// Amend or backfill events
await orb.events.update(eventId, {
event_name: "api_call",
timestamp: correctedTimestamp,
properties: { tokens: 1200 }, // corrected value
});
SQL-Based Metric Definitions
-- Orb lets you define metrics using SQL
-- Total token usage per billing period
SELECT SUM(properties.tokens) AS usage
FROM events
WHERE event_name = 'api_call'
-- Unique active users
SELECT COUNT(DISTINCT properties.user_id) AS usage
FROM events
WHERE event_name = 'api_call'
-- Weighted compute cost by model tier
SELECT SUM(
CASE
WHEN properties.model = 'gpt-4' THEN properties.tokens * 3
WHEN properties.model = 'gpt-3.5' THEN properties.tokens * 1
ELSE properties.tokens * 0.5
END
) AS usage
FROM events
WHERE event_name = 'api_call'
Creating Plans and Price Configurations
// Create a plan with tiered pricing
const plan = await orb.plans.create({
name: "Growth",
description: "Usage-based growth plan",
prices: [
{
name: "API Calls",
item_id: apiCallItemId,
cadence: "monthly",
model_type: "tiered",
tiered_config: {
tiers: [
{ first_unit: "0", last_unit: "10000", unit_amount: "0.000" },
{ first_unit: "10001", last_unit: "100000", unit_amount: "0.002" },
{ first_unit: "100001", last_unit: null, unit_amount: "0.001" },
],
},
},
{
name: "Platform Fee",
item_id: platformFeeItemId,
cadence: "monthly",
model_type: "unit",
unit_config: { unit_amount: "49.00" },
},
],
});
// Matrix pricing — different rates by dimension
const matrixPlan = await orb.plans.create({
name: "Compute Plan",
prices: [
{
name: "Compute",
item_id: computeItemId,
cadence: "monthly",
model_type: "matrix",
matrix_config: {
dimensions: ["region", "instance_type"],
matrix_values: [
{ dimension_values: ["us-east-1", "gpu"], unit_amount: "0.50" },
{ dimension_values: ["us-east-1", "cpu"], unit_amount: "0.05" },
{ dimension_values: ["eu-west-1", "gpu"], unit_amount: "0.60" },
{ dimension_values: ["eu-west-1", "cpu"], unit_amount: "0.06" },
],
},
},
],
});
Subscriptions and Real-Time Usage
// Create a subscription
const subscription = await orb.subscriptions.create({
customer_id: customerId,
plan_id: plan.id,
start_date: new Date().toISOString(),
});
// Fetch real-time usage for current billing period
const usage = await orb.subscriptions.fetchUsage(subscription.id, {
granularity: "day",
});
for (const metric of usage.data) {
console.log(`${metric.billable_metric.name}:`);
for (const point of metric.usage) {
console.log(` ${point.timeframe_start}: ${point.quantity} units`);
}
}
// Get cost estimate before invoice finalization
const costs = await orb.subscriptions.fetchCosts(subscription.id, {
timeframe_start: billingPeriodStart,
timeframe_end: new Date().toISOString(),
});
console.log(`Estimated cost: $${costs.data.reduce(
(sum, c) => sum + parseFloat(c.total), 0
).toFixed(2)}`);
Invoices and Credits
// List invoices
const invoices = await orb.invoices.list({
customer_id: customerId,
status: ["issued", "paid"],
});
// Issue credit grant
await orb.customers.credits.create(customerId, {
entry_type: "increment",
amount: 500, // $500 in credits
per_unit_cost_basis: "1.00",
expiry_date: "2026-12-31",
reason: "Annual prepayment discount",
});
// Check credit balance
const balance = await orb.customers.credits.list(customerId);
console.log(`Credit balance: $${balance.data[0].balance}`);
// Credits automatically apply to invoices
// No additional code needed — Orb deducts credits before charging
Webhooks
import Orb from "orb-billing";
// Verify webhook signature
function verifyOrbWebhook(req: Request, secret: string): boolean {
const signature = req.headers["x-orb-signature"];
const timestamp = req.headers["x-orb-timestamp"];
const body = req.body;
const payload = `${timestamp}.${body}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return signature === `v1=${expected}`;
}
app.post("/webhooks/orb", (req, res) => {
if (!verifyOrbWebhook(req, process.env.ORB_WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = req.body;
switch (event.type) {
case "invoice.issued":
chargeCustomer(event.invoice);
break;
case "invoice.payment_failed":
notifyCustomer(event.invoice);
break;
case "subscription.started":
provisionResources(event.subscription);
break;
}
res.status(200).send("OK");
});
Metronome — Enterprise Usage Billing at Scale
Metronome handles billing for high-scale infrastructure companies — pre-aggregated metering, commits and drawdowns, and enterprise contract management.
Ingesting Usage Events
// Metronome REST API — ingest usage
const response = await fetch("https://api.metronome.com/v1/ingest", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify([
{
customer_id: "cust_42",
event_type: "api_call",
timestamp: new Date().toISOString(),
transaction_id: crypto.randomUUID(),
properties: {
tokens: 1500,
model: "gpt-4",
region: "us-east-1",
},
},
]),
});
// Metronome supports millions of events per second
// Events are pre-aggregated for fast query performance
// Batch ingest via CloudWatch/Datadog/S3 integration
// Configure in Metronome dashboard — no code needed for infra metrics
Billable Metrics
// Create a billable metric
const metric = await fetch("https://api.metronome.com/v1/billable-metrics/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "API Token Usage",
aggregation_type: "sum",
aggregation_key: "tokens",
event_type_filter: {
in_values: ["api_call"],
},
group_keys: [["model"], ["region"]], // group by model and region
}),
});
// Composite metrics for complex pricing
const compositeMetric = await fetch(
"https://api.metronome.com/v1/billable-metrics/create",
{
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Weighted Compute Units",
aggregation_type: "sum",
aggregation_key: "compute_units",
event_type_filter: {
in_values: ["compute"],
},
// Pre-compute weighted values at ingestion time
}),
}
);
Contracts with Commits and Drawdowns
// Create a customer contract with prepaid commits
const contract = await fetch("https://api.metronome.com/v1/contracts/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: "cust_42",
starting_at: "2026-01-01T00:00:00Z",
rate_card_id: rateCardId,
commits: [
{
product_id: apiProductId,
type: "prepaid",
amount: 50000_00, // $50,000 prepaid commit
priority: 1,
applicable_product_ids: [apiProductId, computeProductId],
},
],
// Credits roll over if not used
// Overage charged at list price
}),
});
// Check commit balance
const balance = await fetch(
`https://api.metronome.com/v1/contracts/getBalance?customer_id=cust_42`,
{
headers: { Authorization: `Bearer ${METRONOME_API_KEY}` },
}
);
const data = await balance.json();
console.log(`Remaining commit: $${(data.balance / 100).toFixed(2)}`);
console.log(`Used: $${(data.consumed / 100).toFixed(2)}`);
Rate Cards and Pricing
// Create a rate card with tiered pricing
const rateCard = await fetch("https://api.metronome.com/v1/rate-cards/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Enterprise Rate Card",
rates: [
{
product_id: apiProductId,
entitled: true,
rate_type: "tiered",
tiers: [
{ size: 1000000, price: 0.002 }, // First 1M at $0.002
{ size: 10000000, price: 0.0015 }, // Next 10M at $0.0015
{ size: null, price: 0.001 }, // Beyond at $0.001
],
},
{
product_id: computeProductId,
entitled: true,
rate_type: "flat",
price: 0.05, // $0.05 per compute unit
},
],
}),
});
// Custom rate card overrides per customer
const override = await fetch("https://api.metronome.com/v1/rate-cards/override", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
rate_card_id: rateCard.id,
customer_id: "cust_42",
overrides: [
{
product_id: apiProductId,
rate_type: "tiered",
tiers: [
{ size: 1000000, price: 0.0015 }, // Negotiated discount
{ size: null, price: 0.0008 },
],
},
],
}),
});
Usage Queries and Invoices
// Query aggregated usage
const usage = await fetch("https://api.metronome.com/v1/usage", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: "cust_42",
billable_metric_id: apiMetricId,
window_size: "day",
starting_on: "2026-03-01T00:00:00Z",
ending_before: "2026-03-09T00:00:00Z",
group_by: { key: "model" },
}),
});
const usageData = await usage.json();
for (const window of usageData.data) {
console.log(`${window.start}: ${window.value} tokens (${window.group.model})`);
}
// List invoices
const invoices = await fetch(
`https://api.metronome.com/v1/invoices?customer_id=cust_42&status=finalized`,
{
headers: { Authorization: `Bearer ${METRONOME_API_KEY}` },
}
);
Feature Comparison
| Feature | Lago | Orb | Metronome |
|---|---|---|---|
| Deployment | Self-hosted or cloud | Cloud only | Cloud only |
| License | AGPLv3 (open-source) | Proprietary | Proprietary |
| Metric Definition | UI/API config | SQL-based | API config |
| Pricing Models | Graduated, percentage, package, volume | Tiered, matrix, BPS, package | Tiered, flat, per-unit |
| Event Ingestion | REST + batch | REST + batch + amend | REST + batch + integrations |
| Real-Time Usage | API queries | Real-time dashboards + API | Pre-aggregated queries |
| Commits/Drawdowns | ❌ | Credits | Full commit management |
| Contract Management | Basic subscriptions | Subscriptions + credits | Enterprise contracts |
| Invoice Generation | Built-in + PDF | Automatic | Automatic |
| Webhook Events | HMAC-signed | HMAC-signed | HMAC-signed |
| Payment Integration | Stripe, GoCardless, Adyen | Stripe, payment agnostic | Stripe, payment agnostic |
| Multi-Currency | ✅ | ✅ | ✅ |
| Tax Integration | Basic | Anrok integration | Custom |
| API Style | REST + GraphQL | REST | REST |
| SDK Languages | Node.js, Python, Ruby, Go | Node.js, Python | REST-only (no SDK) |
| Ideal Scale | Startups to mid-market | Mid-market to enterprise | Enterprise / high-volume |
When to Use Each
Choose Lago if:
- You want open-source billing with no vendor lock-in
- You need self-hosted deployment for data residency/compliance
- You want to avoid per-transaction billing platform fees
- Your pricing models are standard (graduated, percentage, volume)
- You're comfortable managing infrastructure (Postgres, Redis, Rails)
Choose Orb if:
- You need complex usage-based pricing with SQL-level flexibility
- You want real-time usage dashboards out of the box
- Matrix pricing (price varies by multiple dimensions) is important
- You need event amendment/backfill capabilities
- Developer experience and API design matter most
Choose Metronome if:
- You process millions of usage events per second
- You sell with prepaid commits, drawdowns, and enterprise contracts
- Your sales team negotiates custom rate cards per customer
- You need integrations with CloudWatch, Datadog, or S3 for metering
- You're an infrastructure/platform company with enterprise sales motions
Methodology
Feature comparison based on Lago v1.x (self-hosted), Orb, and Metronome public API documentation as of March 2026. Pricing models based on published documentation. Code examples use official SDKs where available (Lago Node.js, Orb Node.js) and direct REST API calls for Metronome. Evaluated on: deployment flexibility, pricing model support, event ingestion, real-time usage access, contract management, and developer experience.