SpendLens AILens on AI spend

Node.js / TypeScript SDK

Connect AI spend to the workload that caused it.

Wrap an OpenAI or Anthropic client once, then use async-safe workload and tag context without proxying provider traffic.

Install and configure

npm install spendlensai
# Also install openai or @anthropic-ai/sdk for your provider.
SPENDLENS_API_KEY=sli_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SPENDLENS_PROJECT=shopping-assistant
SPENDLENS_API_BASE_URL=https://spendlensai.dev/backend
SPENDLENS_PROMPT_SAMPLING=template_only
SPENDLENS_DISABLE=0

Node.js 18 or later is required. The package ships native ESM and TypeScript declarations with no runtime dependencies.

SPENDLENS_PROJECT must match an existing dashboard project name or slug. Unknown projects are not created automatically; telemetry is dropped with a warning while the provider response remains unaffected.

OpenAI Chat Completions

Only lines marked ADD are new
import OpenAI from "openai";import { observe, track } from "spendlensai"; const openai = track(new OpenAI()); export const generateReply = observe("customer-support-reply", async (message: string) =>  openai.chat.completions.create({    model: "gpt-4o",    messages: [      { role: "system", content: "Give concise, accurate billing help." },      { role: "user", content: message }    ]  }));

Only the green lines are new: import SpendLens, wrap the existing client once, and name the workload. The OpenAI request, response, and error behavior are preserved.

OpenAI Responses API

const result = await openai.responses.create({
  model: "gpt-4.1",
  instructions: "Extract the requested product filters.",
  input: userInput
});

Reusable instructions can be captured in template_only mode. User input is not included in prompt-template telemetry.

Anthropic Messages

Only lines marked ADD are new
import Anthropic from "@anthropic-ai/sdk";import { observe, track } from "spendlensai"; const anthropic = track(new Anthropic()); export const generateReply = observe("customer-support-reply", async (message: string) =>  anthropic.messages.create({    model: "claude-sonnet-4-6",    max_tokens: 300,    system: "Give concise, accurate billing help.",    messages: [{ role: "user", content: message }]  }));

Anthropic system content is treated as reusable template context. User messages and model responses are not sent.

Workloads with observe()

observe() wraps a sync or async business function and supplies its workload to tracked provider calls inside it. It uses Node AsyncLocalStorage, so overlapping requests do not leak workload labels into one another.

Use observe("workload", function), observe({ workload: "..." }, function), or wrap with observe(options)(function).

Precise endpoint, task, and metadata tags

const response = await openai.tag(
  {
    endpoint: "recommend-products",
    task: "generation",
    metadata: { region: "au", customerTier: "enterprise" }
  },
  () => openai.responses.create({ model: "gpt-4.1", input })
);

Use stable operational labels. Do not put prompts, secrets, emails, or personal information in metadata.

Nested and concurrent tags

await openai.tag(
  { endpoint: "checkout", metadata: { region: "au" } },
  () => openai.tag(
    { task: "extraction", metadata: { experiment: "smaller-model" } },
    () => openai.responses.create({ model: "gpt-4.1-mini", input })
  )
);

Nested metadata is merged and inner values win. Explicit tags override the surrounding observe() workload.

track() options

const openai = track(new OpenAI(), {
  project: "shopping-assistant",
  apiKey: process.env.SPENDLENS_API_KEY,
  apiBaseUrl: "https://spendlensai.dev/backend",
  provider: "openai",
  promptSampling: "template_only",
  debug: false
});
OptionEnvironment variablePurpose
apiKeySPENDLENS_API_KEYTest or live ingestion key.
projectSPENDLENS_PROJECTExisting dashboard project name or slug.
apiBaseUrlSPENDLENS_API_BASE_URLBase URL without /v1.
providerOverride auto-detection with openai or anthropic.
promptSamplingSPENDLENS_PROMPT_SAMPLINGtemplate_only, off, or redacted_sample.
debugSPENDLENS_DEBUGSafe delivery diagnostics without keys or full prompts.

Prompt privacy

template_only captures only reusable system/developer instructions. off captures no prompt text. redacted_sample currently behaves conservatively and does not include user content.

Cache efficiency

OpenAI usage.prompt_tokens_details.cached_tokens and Anthropic cache_read_input_tokens / cache_creation_input_tokens are recorded when returned. Missing cache fields stay unavailable instead of being reported as zero.

Flush and close short processes

await openai.flush(); // Send the current batch.
await openai.close(); // Stop the timer and flush before process exit.

The background interval is unreferenced and does not keep Node alive. Explicitly flush scripts, serverless handlers, tests, jobs, and CLI processes before exit.

Failure and streaming behavior

Telemetry is asynchronous and fail-open. Delivery failures do not replace provider responses. Calls that return streams without final usage data are preserved but not recorded. Set SPENDLENS_DISABLE=1 to return the original provider client and stop all SpendLens activity.