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=0Node.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
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
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
});| Option | Environment variable | Purpose |
|---|---|---|
apiKey | SPENDLENS_API_KEY | Test or live ingestion key. |
project | SPENDLENS_PROJECT | Existing dashboard project name or slug. |
apiBaseUrl | SPENDLENS_API_BASE_URL | Base URL without /v1. |
provider | — | Override auto-detection with openai or anthropic. |
promptSampling | SPENDLENS_PROMPT_SAMPLING | template_only, off, or redacted_sample. |
debug | SPENDLENS_DEBUG | Safe 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.