API & SDK Release Notes

Changelog

Changes to the Bear Lumen API and official SDKs. Endpoints, SDK methods, and breaking changes, newest first.

CLI 0.3.0 / MCP Server 0.3.0 / Node 0.17.0 / Python 0.15.0

Sign in from the CLI, connect deployed agents over HTTP, and see what a key can do

Breaking Changes

  • API keys have a new format: sk_live_ or sk_test_ followed by 40 characters, the last six of which are a checksum. Keys created before this release no longer authenticate. There are no live integrations affected; create a new key from the API Keys page or run bearlumen login
  • bearlumen ping now exits 1 when the key is missing any of the four query scopes (costs:read, revenue:read, usage:read, dimensions:read), and names the preset to select. A script that treated any authenticated ping as success should create its key from the CLI and MCP (read-only) preset

New Features

  • bearlumen login signs the CLI in from the browser with the OAuth device grant. Approve the code on app.bearlumen.com/activate and the CLI stores a 90-day read-only key in ~/.config/bearlumen/credentials.json (mode 0600, keyed by API host so staging and production coexist). bearlumen logout revokes it; bearlumen whoami shows the organization, key name, environment, scopes, and expiry
  • A hosted remote MCP server at https://mcp.bearlumen.com/mcp (Streamable HTTP) for agents that do not run on your machine. It publishes /.well-known/oauth-protected-resource, answers 401 with a WWW-Authenticate header, and MCP clients that implement OAuth (Claude Desktop, Claude Code) sign in with authorization code + PKCE. The token they receive is an ordinary 30-day API key you can see and revoke on the API Keys page
  • The same package ships bear-lumen-mcp-http so you can run the HTTP mode yourself. It never reads a shared BEARLUMEN_API_KEY: every request carries its own bearer credential
  • GET /v1/ping now returns what the key can do: organization_id, environment (live or test), key_name, scopes, and expires_at. The Node and Python SDK ping() methods expose the same fields, the CLI prints them, and the MCP ping tool tells the agent which preset to name when a scope is missing
  • Two API key presets in the dashboard: CLI and MCP (read-only), which grants exactly the four query scopes, and SDK ingestion, which grants usage:write alone. The permissions manifest endpoint lists them under presets
  • Bear Lumen is now an OAuth 2.1 authorization server: metadata at https://api.bearlumen.com/.well-known/oauth-authorization-server, dynamic client registration for public clients, device and authorization-code grants with PKCE S256, and RFC 7009 revocation at POST /v1/oauth/revoke

Improvements

  • The MCP server config blocks in the docs pin @bearlumen/[email protected]. An unpinned npx -y re-resolved the newest version on every client launch, so a breaking release would have changed your tools without you choosing it. Upgrading is now editing the pin
  • Keys issued by OAuth carry the client that obtained them, and the API Keys list shows it as a badge (CLI for a device login, otherwise the client name)
  • The CLI resolves its key in a fixed order: --api-key, then BEARLUMEN_API_KEY, then the deprecated BEAR_LUMEN_API_KEY, then the credentials file for the active base URL. A flag always overrides a login
  • last_used_at on a key is updated from the request log in the background rather than on every authenticated request, so authentication no longer writes to the database
MCP Server 0.2.0 / CLI 0.2.0

One environment variable across the SDK, the CLI, and the MCP server

Breaking Changes

  • The CLI and the MCP server now read BEARLUMEN_API_KEY, the same name the Node and Python SDKs read. If you set BEAR_LUMEN_API_KEY, rename it. The old spelling is still read, and will be removed in a future release
  • BEAR_LUMEN_BASE_URL becomes BEARLUMEN_BASE_URL, on the same terms

New Features

  • One exported key now works everywhere: BearLumen.init() in Node, bearlumen.init() in Python, the bearlumen CLI, and the MCP server all resolve BEARLUMEN_API_KEY
  • Setting the key the quickstart tells you to set is enough to run the CLI. Before this release it failed with "Missing API key" while a valid key sat in the environment

Improvements

  • When both spellings are set, the canonical one wins. The error you get with neither set names only the canonical one, so it cannot send you back to the deprecated spelling
  • Both packages now have tests pinning the variable names, which nothing did before
Node 0.16.0 / Python 0.14.0

Prompt caching is now measured on the Chat Completions API

Breaking Changes

  • If you use the OpenAI Chat Completions API, your reported input tokens and your reported cost both drop for cached prompt traffic. Cached tokens were not captured at all, so they were charged at the full input rate
  • If you stream responses, your reported token split changes. Streamed calls were extracted by a different code path from complete responses, and the two had drifted: streamed Chat Completions dropped cached tokens, and streamed Responses API calls counted them twice
  • A date range that spans this release contains both the old and the corrected numbers, so expect a step change at the release date rather than a gradual one. Events recorded before the release keep the old values

New Features

  • Cache hit rate and cache savings now appear for Chat Completions traffic. This is the most-used OpenAI API, so for most OpenAI workloads prompt caching was previously invisible in your dashboard

Improvements

  • Your totals are unchanged. Input plus cache-read still equals the prompt token count OpenAI reports
  • Nothing to change in your code. If you already pass your client to wrap(), or hand a response to track(), the cached split arrives on the next release
  • A streamed call and a non-streamed call to the same model now produce the same numbers. So do the Node and Python SDKs, which had diverged on streamed Responses API calls
  • Providers that implement OpenAI usage reporting, such as OpenRouter and LiteLLM, get the same treatment. Providers that report no caching are unaffected
  • Gemini and Bedrock still do not report cache tokens to Bear Lumen. Gemini is a straightforward addition; Bedrock needs its caching semantics confirmed per model family first, because it fronts providers that count cached tokens differently
Node 0.15.0 / Python 0.13.0

Cached prompt tokens are no longer counted twice

Breaking Changes

  • If you use the OpenAI Responses API, your reported input tokens and your reported cost both drop for cached prompt traffic. Cached tokens were being counted as input tokens as well as cached tokens, so they were charged at both rates
  • A date range that spans this release contains both the old and the corrected split, so expect a step down at the release date rather than a gradual change. Events recorded before the release keep the old split

Improvements

  • The more of your prompt hits the cache, the larger the overstatement was, so the correction is biggest on exactly the workloads caching is meant to make cheaper
  • Cache hit rate was understated by the same cause and is now correct: 80 cached tokens out of 100 read as 44% before, and as 80% now
  • Your totals are unchanged. Input plus cache-read still equals the input token count OpenAI reports
  • Nothing to change in your code. If you already pass your client to wrap(), the corrected split arrives on the next release
  • Anthropic traffic was never affected. It reports cached tokens separately from input tokens already, which is the convention this brings the OpenAI Responses API into line with
Python 0.12.0

Call timing in the Python SDK

New Features

  • Every call through a client you passed to wrap() now carries how long it took, on all four paths: sync, async, streaming and non-streaming
  • Streaming responses are measured to the end of consumption, not to the moment the response started. Headers arriving is not the generation finishing
  • track(started_at=..., duration_ms=...) are typed keyword parameters, so you can time work the SDK cannot wrap: a hosted transcription job, an image render, a search API

Improvements

  • Nothing to change at your call sites. If you already use wrap(), timing starts arriving on the next release
  • Durations come from a monotonic clock around the provider call, so a clock adjustment mid-call cannot record a negative or impossible time
  • A naive datetime is accepted and read as UTC, because datetime.now() is what most callers reach for first
  • A duration you supply yourself is checked before the request leaves your process, instead of being rejected later and dropping the event
  • A call refused by a budget cap is never timed. It did not run
Node 0.14.0

Call timing on wrapped provider calls

New Features

  • Every call through a client you passed to wrap() now carries how long it took, so you can see which step in a run is slow as well as which one is expensive
  • Streaming responses are measured to the end of consumption, not to the moment the response started. Headers arriving is not the generation finishing
  • startedAt and durationMs on track() are documented for the first time. They have been accepted since 0.13.0 and let you supply timing when you measure a call yourself

Improvements

  • Nothing to change at your call sites. If you already use wrap(), timing starts arriving on the next release
  • Durations come from a monotonic clock around the provider call, so a clock adjustment mid-call cannot record a negative or impossible time
  • A duration you supply yourself is now checked before the request leaves your process, instead of being rejected later and dropping the event
  • A call refused by a budget cap, and a call your provider rejected, are never timed. Neither one ran to completion
Node 0.13.0 / Python 0.11.0

Relative date windows and one-line setup

New Features

  • Every dated read method accepts a relative window: lastDays (Node) or last_days (Python), as an alternative to an explicit startDate / endDate pair
  • BearLumen.init() / getBearLumen() (Node) and bearlumen.init() / bearlumen.get_client() (Python) give you a process-global client, so you configure once at startup instead of threading an instance through your call graph
  • Both read the BEARLUMEN_API_KEY environment variable when no key is passed

Improvements

  • Relative windows resolve client-side and end tomorrow, so an event you tracked seconds ago is always inside the window you just asked for
  • A dated read method called with no window now throws before the request leaves your process, naming both accepted forms
  • The wire contract is unchanged. Explicit date ranges keep working exactly as before
  • Python 0.11.0 is the first non-alpha release of the bearlumen package
Node 0.12.0 / Python 0.10.0a1

Span budgets and cross-process tracing

New Features

  • Span budgets: declare budget: { usd, onExceeded } (Node) or budget={"usd": ..., "on_exceeded": ...} (Python) on a span, and every provider call inside it is checked before it executes. Budgets nest, so a child span debits every enclosing cap
  • New BudgetExceededError, exported from both SDKs, carrying the cap, the spend so far, and the span name. It surfaces to your code like any other provider error rather than being swallowed into telemetry
  • The non-blocking alternative is onExceeded: "flag" (Node) or on_exceeded="flag" (Python): the breach stamps metadata on subsequent events and calls continue
  • traceContext() / trace_context() returns the active span as a W3C trace context carrier, and the parent span option resumes a trace from it, so a queue, worker, or webhook continues the caller trace
  • New GET /rate-cards/estimation-table returns effective per-model rates, which the SDKs refresh into their bundled price table for budget estimation

Improvements

  • Carriers are standard W3C traceparent and tracestate, so OpenTelemetry-instrumented transports carry them natively
  • Budget estimates are client-side and deliberately conservative. The billing-grade cost ledger stays server-side
  • Carrier bytes are cross-checked between the two SDKs by a shared fixture, so a trace started in Python resumes in Node
Node 0.11.0 / Python 0.9.0a1

Spans, outcomes, and cost per unit of value

New Features

  • Unit-of-work spans: withSpan() and traced() (Node), bear.span() as a decorator or context manager (Python). Every tracked call inside a span, at any async depth, shares one session and links to its caller, so the full call tree reconstructs
  • Outcomes: declare outcome on the span that produces a unit of value and every cost event inside carries it, with one zero-cost terminal event recording whether the unit completed or failed
  • setAttribute() / set_attribute() adds a metadata label to the running span, applying forward to events tracked from that point on
  • costs.byOutcome() / costs.by_outcome() reads unit economics back: completed, failed, and unresolved units side by side with cost per completed unit
  • New GET /attribution-data/by-outcome and GET /attribution-data/sessions/{sessionId}/tree back the outcome and call-tree reads

Improvements

  • Span context is async-local per client instance, so parallel branches never cross-wire and two clients in one process stay isolated
  • Context is captured at call time, so a response that settles after its span exits still attributes to the span that issued it
  • cost_per_completed_unit is null when nothing completed, never zero as a fallback, so an empty period is distinguishable from a free one
  • A failing outcome rethrows your original error unchanged, and the cost already spent stays attributed
Node 0.10.0 / Python 0.8.0a1

Automatic tracking for provider clients

New Features

  • wrap(): wrap an OpenAI or Anthropic client once and every call through it is tracked automatically, with no per-call tracking code
  • Streaming is supported through wrapped clients. Iterate the chunks as normal and usage is recorded on consumption
  • First-class session and span fields on track(): sessionId, sessionType, spanId, parentSpanId in Node, and session_id, session_type, span_id, parent_span_id in Python. They are sent as top-level wire fields rather than by metadata convention
  • Auto-detection for OpenAI Embeddings and Responses API payloads, so those calls record real models and token counts instead of unknown

Improvements

  • A wrapped client is transparent: identical arguments, untouched responses, and provider errors that pass through unmodified. Tracking failures are routed to your onError callback and never mask a provider error
  • Session and span fields are validated at the call site, because a server-side rejection would otherwise drop the event silently during a background flush
  • Wrapping an already-wrapped client is a no-op, and an unrecognized client shape throws an error listing what is supported
MCP Server 0.1.0 / CLI 0.1.0

MCP server and command-line interface

New Features

  • @bearlumen/mcp-server exposes 15 read-only tools across costs, margins, insights, usage, and attribution, over stdio for any MCP client including Claude Desktop, Cursor, and Claude Code
  • @bearlumen/cli brings the same surface to the terminal as the bearlumen command, with human-readable tables by default and --json for piping into tools like jq
  • Both authenticate with the BEAR_LUMEN_API_KEY environment variable, with BEAR_LUMEN_BASE_URL to point at a non-production host
  • The MCP server ships SKILL.md, an agent-facing reference for each tool, the date conventions, and common workflows

Improvements

  • The command surface is one-to-one with the MCP tool surface, so a script and an agent see identical data
  • Both are thin read-only wrappers over @bearlumen/node-sdk, inheriting its request signing and response parsing so they stay in lockstep with the public API
Node 0.9.0

The Node SDK is now isomorphic

Breaking Changes

  • Minimum Node.js version is now 20. Request signing and event ID generation moved from node:crypto to WebCrypto, which Node exposes globally from version 19. Node 18 reached end of life in April 2025

New Features

  • The SDK now bundles cleanly for browsers and edge runtimes including Cloudflare Workers, Deno, and Bun, in addition to Node

Improvements

  • HMAC request signing is byte-identical to previous versions, verified against the backend canonical test vectors
Node 0.8.0 / Python 0.7.0a1

The environment option is removed

Breaking Changes

  • The environment config option is gone from both SDKs. Production is the default. To point an SDK at a non-default API, set baseUrl (Node) or base_url (Python) explicitly, keeping the /v1 suffix. Remove any environment argument when upgrading
Node 0.7.0 / Python 0.6.0a1

snake_case response bodies, and the Python SDK on PyPI

Breaking Changes

  • Response fields are snake_case in both SDKs, returned exactly as the API sends them with no client-side casing transform. Read result.total_cost rather than result.totalCost, point.period_start rather than point.periodStart, and so on across cost, margin, insights, dimension, attribution, and usage responses
  • The usage-event ingestion contract was renamed: sdk_event_id is now idempotency_key, the wire field cost is now cost_override, and the flat experiment_id and variant pair is now a single nested experiment object with both fields required when present

New Features

  • First PyPI release of the bearlumen package, with sync and async clients at parity

Improvements

  • Client-synthesized types stay camelCase in Node by design: config, track options, query parameters, and the error envelope
  • The costOverride input option keeps its camelCase spelling. Only the wire field name changed
  • A contract test in each SDK fails the build if a camelCase field ever leaks onto the wire
API 2025-10-18

Public API and official SDKs

New Features

  • Stable REST API under /v1 (dated version 2025-10-18)
  • Cost, margin, usage, attribution, insights, and dimension endpoints
  • Official Node.js / TypeScript SDK (@bearlumen/node-sdk)
  • Official Python SDK (bearlumen) with sync and async clients

Improvements

  • API key authentication with scoped, restricted keys
  • OpenAPI 3 specification published at /openapi.json