Lago is a trademark of its respective owner, used here for identification and migration comparison only. No endorsement, partnership, or affiliation is implied.
- Metering lives behind a Lago API key, and each customer is a Lago customer record — an identity that exists only inside Lago, unrelated to where the same agent’s cards, email, and permissions live.
- Whether the agent’s plan actually lets it use a capability is an entitlement you configure in Lago and then re-enforce yourself at the call site — Lago meters and can flag limits, but the primitive access it is “entitling” is governed by a different system.
- “Which plan is this agent on, what has it used this period, and is it allowed to do this right now?” is answered in Lago for usage, and in your own permission code for access — two sources of truth for one decision.
- A Naive plan maps a key to an Account Kit (what the agent may do) plus per-primitive quotas (how much). Subscribing a tenant assigns that kit — so plan tier → permissions → quota is one call, not a Lago config plus separate entitlement code.
- Usage is metered automatically on the primitive call itself — no event pipeline to build,
secure, and de-duplicate. When a tenant exceeds a quota, the next call is refused with
429 rate_limitedat execution time. - Every metered call lands in the same per-user activity log as that agent’s card spend, email sends, and KYC events — one accountability trail.
Tested against: the Lago Node client
lago-javascript-client v1.48.0 (June
2026), against the Lago REST API (base https://api.getlago.com/api/v1, Authorization: Bearer,
/billable_metrics, /plans, /customers, /subscriptions, /events, /customers/{id}/current_usage
endpoints, docs snapshot July 2026), and the Naive Node SDK
@usenaive-sdk/server against the Naive API (base https://api.usenaive.ai/v1,
/v1/plans + /v1/users/:id/billing/* endpoints, docs snapshot July 2026).Version notes:- Scope differs. Lago bills any SaaS customer. Naive customer-billing bills a
tenant user — the same handle (
naive.forUser(id)) that reaches the agent’s cards, email, phone, vault, and KYC. - Naive does NOT own the charge. Naive owns plan → permissions → quota → usage. Your app keeps
its Stripe charge (Naive plans carry an optional
stripePriceIdto link the two). If you rely on Lago for invoice generation, payment collection, coupons, wallets, or complex pricing, that stays with Lago/Stripe — see what doesn’t map yet. - Metering is built-in, not custom. Lago meters any
codeyou define with any aggregation. Naive meters a fixed, documented set of primitives (seo,aeo,email) as a per-call count — you do not define custom billable metrics. See gaps. - Ingestion is automatic. Lago requires you to
POST /eventson every billable action. Naive records one usage event per successful metered primitive call — there is no ingestion endpoint to call.
Concept map
| Lago | Naive | Notes |
|---|---|---|
Client(apiKey) | new Naive({ apiKey }) then naive.forUser(id) | Server-side credential in both cases |
Customer (external_customer_id) via customers.createCustomer | tenant user via naive.users.create / naive.forUser(id) | The Naive id also reaches cards, email, phone, KYC |
Billable metric (billableMetrics.createBillableMetric, code + aggregation) | Built-in metered primitives (seo, aeo, email) | You don’t define custom metrics — see gaps |
Plan (plans.createPlan, charges per metric) | naive.plans.upsert({ key, name, accountKitId, quotas, period }) → POST /v1/plans | A Naive plan = an Account Kit + per-primitive call quotas (caps, not priced charges) |
| — (entitlements configured separately) | The plan’s accountKitId decides which primitives the agent may use | Permission and plan are the same object — see gain #2 |
Subscription (subscriptions.createSubscription({ subscription: { external_customer_id, plan_code } })) | naive.forUser(id).billing.setSubscription({ planKey }) → PUT /v1/users/:id/billing/subscription | assignKit (default true) also applies the plan’s Account Kit in the same call |
Usage event (events.createEvent({ event: { code, external_subscription_id, properties } }) on every action) | Automatic — one usage event recorded per successful metered primitive call | Gain: no event pipeline to build, secure, or de-duplicate |
Current usage (customers.findCustomerCurrentUsage(id, { external_subscription_id })) | naive.forUser(id).billing.usage() → GET /v1/users/:id/billing/usage | Returns { plan, status, period, quotas, usage } |
| Overage / soft usage limit | Hard quota → 429 rate_limited on the next call | Enforced at execution time, not reconciled on an invoice |
stripe/adyen/gocardless payment provider on the customer | Your app keeps its own Stripe charge; plan carries optional stripePriceId | Naive owns plan/quota/usage, not the charge |
| Invoices, wallets (prepaid credits), coupons, add-ons, credit notes, taxes, multi-currency | — | Not provided — see gaps |
| Revenue analytics (MRR, usage trends) | Per-tenant usage rollup only (billing.usage()) | No cross-customer revenue analytics |
Before / after: the core path
The path that matters for a usage-billed agent product is define a plan → subscribe a customer → meter their usage → enforce the quota. Here it is on both platforms.- The plan carries permissions. A Naive plan references an
accountKitId; subscribing assigns it. There is no separate “entitlement” object to keep in sync — the plan is the entitlement. - No event ingestion. Delete your
events.createEvent(...)calls. Naive metersseo,aeo, andemailon the primitive call itself; you never post usage. - Quotas are caps, not charges. Lago prices each metered unit; Naive quotas are per-period call
limits that return
429when exceeded. Pricing/collection stays in your Stripe. - Usage rollup is per tenant.
billing.usage()returns the tenant’s quotas + consumption for the current period — the analogue offindCustomerCurrentUsage.
Minimal viable migration
The smallest swap that keeps a working, metered product running is plan → subscribe → let metering run.Install the SDK and set your key
NAIVE_API_KEY (a server-side key from the dashboard).Recreate your plans as Account Kit + quotas
For each Lago plan, create an Account Kit enabling the primitives
that tier may use, then
naive.plans.upsert({ key, name, accountKitId, quotas, period }). Map
your Lago plan code → Naive plan key, and your metered limits → quotas (per-primitive call
caps). Carry your Stripe price across via the optional stripePriceId.Reflect subscriptions from your Stripe webhook
Replace
subscriptions.createSubscription(...) with
naive.forUser(tenantId).billing.setSubscription({ planKey, status, stripeCustomerId, stripeSubscriptionId, currentPeriodEnd }).
Drive it from the same Stripe webhook you already run. assignKit defaults to true, so the
plan’s Account Kit is applied in the same call.Delete the event ingestion path
Remove your
events.createEvent(...) calls for the metered primitives. Naive records usage
automatically on each successful seo, aeo, and email call — there is no ingestion endpoint.Swap the usage read
Map
customers.findCustomerCurrentUsage(id, ...) → naive.forUser(id).billing.usage() for the
current-period rollup + quotas.Consolidate further once you’re on Naive
This is where the migration pays for itself. In Lago, a plan meters usage and (via entitlements) can describe access — but the primitives it entitles are governed elsewhere, so “billing plan” and “permission set” are two systems you keep in sync. On Naive, the plan is the Account Kit: the object that bills the customer is the object that governs their agent.Gain #1 — one identity across billing and capability
- With Lago, a customer is a billing record; the agent’s cards, email, phone, and permissions live in
other systems. With Naive,
naive.forUser(acme.id)is a single handle to the agent’s plan and primitives and permissions and audit trail — no separate customer record to reconcile. - Downgrading a customer’s plan re-assigns their Account Kit, which immediately changes what their agent may do — no second write to a permission system, no drift between “what they pay for” and “what they can touch”.
Gain #2 — execution-time permission enforcement
- A plan’s
accountKitIddecides which primitives the agent may use, and quotas cap how much — both enforced at the moment of the call, not reconciled later on an invoice.
- The agent’s code path stays the same for every tier. A tenant whose plan doesn’t enable
seois refused withforbidden; a tenant over itsaeoquota is refused with429 rate_limited— with no code change on your side:
- In Lago, exceeding a metered limit typically means an overage charge reconciled on the next invoice; on Naive the quota is a hard, execution-time gate. The two models are complementary — keep Stripe for the money, let Naive stop the call.
Gain #3 — unified accountability
- Every metered call records the acting agent and lands in one per-user activity log — alongside that customer’s card spend, email sends, and KYC events, not in a separate Lago usage stream:
- That is the question that is hard to answer when usage lives in Lago, permissions live in your code, and the agent’s spend lives in Stripe. Under Naive it is a single query.
What does not map yet
A migration guide that hides gaps is worse than none. The metering + entitlement + quota layer maps cleanly and fuses with the agent’s permission model — but Lago is a full billing platform, and several of its capabilities have no equivalent on Naive’s customer-billing primitive today. Naive is explicit that it owns plan → permissions → quota → usage, not the charge. Check this list against your app before you commit.| Lago feature | Status on Naive | Workaround |
|---|---|---|
| Invoicing + payment collection (Stripe/Adyen/GoCardless, dunning, retries) | Not provided — Naive doesn’t generate invoices or move money | Keep your Stripe charge; link it via the plan’s optional stripePriceId and drive setSubscription from your Stripe webhook |
Custom billable metrics (any code, count/sum/max/unique/weighted aggregation, charge filters) | Not provided — Naive meters a fixed set of primitives as a per-call count | Meter custom app dimensions in your own store / keep Lago for those metrics |
| Metered primitive coverage | Documented metered set is seo, aeo, email | Other primitives are gated + budgeted, but not plan-quota-metered; track them via logs / billing |
| Priced usage / overage billing | Not provided — quotas are hard caps (429), not priced charges | Price usage in Stripe; use Naive quotas as the enforcement gate |
| Wallets / prepaid credits, coupons, add-ons, credit notes | Not provided | Keep in Lago/Stripe |
| Taxes + multi-currency, billing entities | Not provided | Handle in your Stripe/tax stack |
| Revenue analytics (MRR, usage trends, cross-customer) | Per-tenant usage rollup only (billing.usage()) | Aggregate from your own store or keep Lago analytics |
| Batch event ingestion, historical event backfill/editing | Not applicable — metering is automatic per call, not ingested | N/A (no ingestion endpoint) |
| Aggregation period flexibility | period is month or day | Choose the closest of the two |
Where to go next
- Customer Billing primitive — plans → Account Kits, per-tenant subscriptions, metered usage, and quota enforcement
billingSDK sub-client — typedplans.upsert/setSubscription/usage- Account Kits — the permission object a plan assigns, enforced per user at execution time
- Tenant users — the identity a subscription, usage, cards, email, and KYC all hang off
- Billing & Credits — your own Naive subscription (distinct from billing your customers)
- Logs — the unified per-user activity trail every metered call lands in