Lago's plans + metering + entitlements → the Naive customer-billing primitive
- 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.
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
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
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
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
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
customers.findCustomerCurrentUsage(id, ...) → naive.forUser(id).billing.usage() for the
current-period rollup + quotas.Keep Stripe for the charge, then ship
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.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
Related reading (blog)
- Why consolidate agent infra on one governed identity — migration thesis behind this guide
- Building AI Agents Into Your SaaS — tenant user anchor for every migration
- How To Build An Agentic Email Marketing Platform — customer billing tutorial