Lago alternative: Usage Lab vs Lago, honestly

Lago is a full open-source billing platform: metering, plans, invoicing, credits and payments. Usage Lab is a small hosted metering API that keeps Stripe as your biller. Here's what each costs, where each fits, and how to move events across.

Every Lago fact below was checked on the official source linked next to it, as of Oct 21, 2026. Where Lago publishes no price we say "Contact sales". Usage Lab is our product; corrections are welcome at canada.site.ops@gmail.com.

Lago facts (as of Oct 21, 2026)

TopicFactSource
Open Source$0 software license under AGPL-3.0, self-hosted. Infrastructure and operating costs are not included.Lago docs: pricing and packaging
Premium on CloudCustom quote, on Lago-managed cloud: Contact sales. Lago "does not publish a universal monthly or annual Premium price"; the currency is set in the order form.Lago docs: pricing, getlago.com/pricing
Premium self-hostedCustom quote: Contact sales. You run the infrastructure.Lago docs: pricing
LicenceGNU Affero General Public License v3.0 (AGPL-3.0), actively maintained on GitHub.github.com/getlago/lago
Billing featuresBillable metrics and usage ingestion, plans and subscriptions, invoice generation, credit notes, entitlements, prepaid credits, progressive billing, and payments integration. Some items, such as tax integrations and automatic dunning, are Premium add-ons.getlago.com/pricing
Credits and walletsPrepaid credits in wallets: up to 6 active wallets per customer, with top-ups, scoping and expiry.Wallets docs
StripeA native Stripe integration collects payments automatically when Lago generates invoices. Lago is the biller; Stripe takes the payments.Stripe integration docs
Eventstransaction_id, external_subscription_id, code, timestamp and properties. transaction_id is used for deduplication; the ClickHouse pipeline keys on transaction_id + timestamp.Ingesting usage, Events API

Usage Lab pricing for comparison: Free CA$0 (100k events, 3 meters a month), Pro CA$12/month (2M events, 10 meters), Ultra CA$36/month (20M events, 100 meters). See pricing.

When Lago is the better fit

Where Usage Lab fits

Usage Lab is early access and small: no invoicing, credits, wallets, entitlements or SLAs.

Migration: Lago events to Usage Lab events

Lago eventUsage Lab eventNotes
codemeterCreate one Usage Lab meter per billable metric code (sum, count or unique_count).
external_subscription_idsubscriptionThe customer or subscription you bill. Totals are per subscription per month.
transaction_ididempotency_key (or the Idempotency-Key header)Keep the same id so a retry counts once. Usage Lab never replaces an event: the same key with a different body returns 409.
timestamptimestampUnix seconds work as-is, and so does ISO 8601. Up to 30 days late is accepted.
properties[field_name]valueThe field your sum metric aggregates. Count metrics need no value; unique count uses unique_value.
rest of propertiespropertiesStored with the event, not aggregated.

Lago event (the "event" object of Lago's POST /api/v1/events). Input format, not a command.

{
  "transaction_id": "inf_2026-10_sub8832_00001",
  "external_subscription_id": "sub_8832",
  "code": "llm_tokens",
  "timestamp": 1790812800,
  "properties": { "tokens": 1500, "model": "gpt-4o-mini" }
}

The same event for Usage Lab (one item of POST /api/v1/usage {"events": [...]}). Not a command.

{
  "meter": "llm_tokens",
  "subscription": "sub_8832",
  "idempotency_key": "inf_2026-10_sub8832_00001",
  "timestamp": 1790812800,
  "value": 1500,
  "properties": { "model": "gpt-4o-mini" }
}

This script creates the meters, converts example Lago events, sends them as one batch, sends the batch again to show the retry counts once, and prints this month's totals. It needs only Python's standard library and USAGE_LAB_KEY (a free key from /signup/). Save it as lago_to_usage_lab.py and run python3 lago_to_usage_lab.py.

# lago_to_usage_lab.py: Python 3.8+, standard library only. Needs USAGE_LAB_KEY in the environment.
# Re-sends Lago-style usage events to Usage Lab. Safe to re-run: same transaction_id = counted once.
import json, os, sys, urllib.error, urllib.request
from datetime import datetime, timezone

API = "https://usage-lab.pages.dev/api/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["USAGE_LAB_KEY"], "Content-Type": "application/json",
           "User-Agent": "lago-to-usage-lab/1.0"}  # any descriptive User-Agent; see the note below the sample

# Your Lago billable metrics, re-declared: code -> (Usage Lab meter, aggregation, field_name)
METRICS = {
    "llm_tokens":   ("llm_tokens",   "sum",   "tokens"),  # Lago sum_agg on properties.tokens
    "api_requests": ("api_requests", "count", None),      # Lago count_agg: no field needed
}

def call(method, path, body=None):
    data = None if body is None else json.dumps(body).encode()
    req = urllib.request.Request(API + path, data=data, headers=HEADERS, method=method)
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, json.load(r)
    except urllib.error.HTTPError as e:
        return e.code, json.load(e)

def to_usage_lab(ev):
    meter, _agg, field = METRICS[ev["code"]]                   # code -> meter
    props = dict(ev.get("properties") or {})
    out = {"meter": meter,
           "subscription": ev["external_subscription_id"],     # external_subscription_id -> subscription
           "idempotency_key": ev["transaction_id"]}            # transaction_id -> idempotency key
    if ev.get("timestamp") is not None:
        out["timestamp"] = ev["timestamp"]                     # unix seconds work as-is (ISO 8601 too)
    if field:
        out["value"] = float(props.pop(field))                 # properties[field_name] -> value
    if props:
        out["properties"] = props                              # the rest stays as properties
    return out

# 1. Create the meters (409 meter_exists on a re-run is fine)
for _code, (key, agg, _field) in METRICS.items():
    status, body = call("POST", "/meters", {"key": key, "aggregation": agg, "name": key})
    if status not in (201, 409):
        sys.exit(f"create meter {key}: HTTP {status} {body}")

# 2. Example events, as you would have sent them to Lago's POST /api/v1/events
now = datetime.now(timezone.utc)
month = now.strftime("%Y-%m")
ts = int(datetime(now.year, now.month, 1, tzinfo=timezone.utc).timestamp())
lago_events = [
    {"transaction_id": f"inf_{month}_sub8832_00001", "external_subscription_id": "sub_8832",
     "code": "llm_tokens", "timestamp": ts, "properties": {"tokens": 1500, "model": "gpt-4o-mini"}},
    {"transaction_id": f"inf_{month}_sub8832_00002", "external_subscription_id": "sub_8832",
     "code": "llm_tokens", "timestamp": ts, "properties": {"tokens": 620, "model": "gpt-4o-mini"}},
    {"transaction_id": f"req_{month}_sub9001_00001", "external_subscription_id": "sub_9001",
     "code": "api_requests", "timestamp": ts, "properties": {"route": "/v1/search"}},
]

# 3. Send as one batch, then re-send it: the retry is counted once
batch = {"events": [to_usage_lab(e) for e in lago_events]}
for attempt in ("first send", "retry"):
    status, body = call("POST", "/usage", batch)
    if status >= 400:
        sys.exit(f"{attempt}: HTTP {status} {body}")
    print(attempt, body["summary"])

# 4. Read this month's totals per subscription
for key in ("llm_tokens", "api_requests"):
    status, body = call("GET", f"/meters/{key}/rollups?month={month}")
    if status != 200:
        sys.exit(f"rollups {key}: HTTP {status} {body}")
    for r in body["rollups"]:
        print(key, r["subscription"], r["quantity"])

Example output, not a command.

first send {'created': 3, 'duplicate': 0, 'conflict': 0}
retry {'created': 0, 'duplicate': 3, 'conflict': 0}
llm_tokens sub_8832 2120
api_requests sub_9001 1

User-Agent note: the Cloudflare edge in front of *.pages.dev rejects Python's default Python-urllib User-Agent (error 1010) before the request reaches us. Any descriptive User-Agent works, including AI agents, bots and scripts, which are welcome.

To move history, export events from Lago, feed them through to_usage_lab() in batches of up to 100, and backfill only the last 30 days. Keep invoices and older usage in Lago or your warehouse.

More comparisons

FAQ

Is Lago open source?

Yes. As of Oct 21, 2026 the getlago/lago repository on GitHub is licensed under AGPL-3.0 and actively maintained. Lago Open Source is a $0 software license that you self-host; you pay for and run the infrastructure.

How much does Lago cost?

As of Oct 21, 2026: Lago Open Source is a $0 software license under AGPL-3.0 (self-hosted, infrastructure not included). Lago Premium, on Lago Cloud or self-hosted, is a custom quote: Lago does not publish a fixed price, so it is Contact sales.

Does Lago have a hosted cloud?

Yes. Lago Premium is available on Lago-managed Cloud and as a self-hosted deployment. The free open-source edition is self-hosted only.

Can I move my Lago events to Usage Lab?

Yes. Map code to meter, external_subscription_id to subscription, transaction_id to the idempotency key, timestamp to timestamp (unix seconds work as-is), the billable metric's field_name in properties to value, and keep the rest as properties. The Python sample on this page runs as-is.

When should I stay on Lago?

When you need a full billing platform: invoicing, credit notes, prepaid credits and wallets, plans and subscriptions, dunning, taxes, or an open-source biller you host yourself. Usage Lab only meters usage and leaves billing to Stripe.

What does Usage Lab cost?

Free: CA$0 for 100,000 events and 3 meters a month. Pro: CA$12 a month for 2M events and 10 meters. Ultra: CA$36 a month for 20M events and 100 meters. Flat prices, never a percentage of your billings.