Configuration
Both SDKs expose the same surface with idiomatic naming. Python takes seconds, JavaScript takes milliseconds.Choosing a subscription
Lago needs to know which customer to bill. The SDK resolvesexternal_subscription_id in this order:
- Per-call override — highest precedence
- Context-bound — set once per request handler, propagating across async boundaries via
contextvars(Python) andAsyncLocalStorage(Node) - Default at init — the fallback
dimensions are merged with any set at wrap() time, with the per-call keys winning. They land on the event as properties, so they work as charge filters.
Billing a provider the SDK does not wrap
emit() and CanonicalUsage are public. Together they let you bill anything — a provider with no wrapper, an endpoint wrap() does not patch, or usage read from a log after the fact.
emit() accepts the same mode and markup overrides as a per-call extra_lago, plus two arguments meant for backfills:
emit() is documented as never raising. Anything that goes wrong inside it is caught and routed to on_error.Errors
Both SDKs export the same classes:LagoSDKError— base class for every SDK-raised errorLagoApiError— non-2xx from Lago. CarriesstatusandbodyLagoConfigError— invalid configuration at initUnknownClientError—wrap()was called on a client the SDK does not recognize. SubclassesLagoConfigErrorPricingUnavailableError— price mode could not resolve a price. Surfaced throughon_error, never raised at the call site
on_error on day one:
where argument names the phase that failed: emit, send_batch, pricing, one of pricing.fetch_openrouter / pricing.fetch_bedrock / pricing.fetch_cloudflare_workers_ai / pricing.fetch_mistral_aliases, or overflow (JavaScript only).
Behaviour under failure
The design promise: your LLM call never breaks because of the SDK. Anything that goes wrong inside instrumentation is caught, logged, and either retried or absorbed.
Retries. Transient failures re-prepend the batch and back off 1s → 2s → 4s → 8s → 16s → 32s →
max_retry_seconds (60s default), resetting to zero on the first success. Permanent failures never accrue backoff.
Overflow. The buffer is a bounded FIFO. When it is full, the oldest event is dropped to make room, on the reasoning that recent events best reflect what the customer is doing now. Python logs a warning; JavaScript also fires onError(err, "overflow").
flush() waits for the buffer to empty, not for the in-flight request to come back. A batch already handed to the worker is invisible to it, so flush() can return True a moment before that POST lands. For a hard guarantee at process exit, use shutdown().Next steps
Billing
Token mode, price mode, and the plan setup for each.
Overview
Quickstart and the provider list.