Skip to main content

Configuration

Both SDKs expose the same surface with idiomatic naming. Python takes seconds, JavaScript takes milliseconds.
The Python constructor also takes api_key, api_url and default_subscription_id directly. When you pass both a constructor argument and a config, the constructor argument wins — so set api_url on the constructor, not only on the config.

Choosing a subscription

Lago needs to know which customer to bill. The SDK resolves external_subscription_id in this order:
  1. Per-call override — highest precedence
  2. Context-bound — set once per request handler, propagating across async boundaries via contextvars (Python) and AsyncLocalStorage (Node)
  3. Default at init — the fallback
Per-call 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.
If none of the three resolve, the event is dropped and an error is logged. Make sure at least one is set before your first call.

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 error
  • LagoApiError — non-2xx from Lago. Carries status and body
  • LagoConfigError — invalid configuration at init
  • UnknownClientErrorwrap() was called on a client the SDK does not recognize. Subclasses LagoConfigError
  • PricingUnavailableError — price mode could not resolve a price. Surfaced through on_error, never raised at the call site
Because instrumentation failures are silent by design, wire on_error on day one:
The 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.