Skip to main content
The TypeScript SDK wraps your existing LLM client instances and automatically captures latency, token counts, costs, and errors for every call — with no changes to your application logic beyond initialization.

Installation

1

Install the package

All provider integrations are optional peer dependencies. Install only what you use:
2

Initialize Zespan

Call zespan.init() once at application startup, before any LLM calls are made.
3

Wrap your LLM client

Pass your client instance through the appropriate wrapper function. The wrapper returns the same client — your existing code continues to work unchanged.

Init options

zespan.init(options) accepts the following configuration:
string
required
Your Zespan API key. Must start with zsp_. Find this in your project settings.
string
default:"production"
Environment label attached to every event. Use "staging" or "development" to separate traces by environment, or any custom string that matches an environment you’ve created (Settings → Environments) in the dashboard.A value that doesn’t match any environment on the project (and isn’t "production"/"development", which alias to the seeded prod/dev) is never dropped — it’s still stored under the string you sent, with a warning logged on the project (throttled to once an hour). See Environments for the full alias and asymmetry behavior.
boolean
default:"true"
When true (default), prompt and completion text are stored alongside traces with PII redaction applied before transmission. Set to false to disable prompt storage entirely. For Google ADK, false also omits tool arguments, tool results, decision rationale, and error text — see Google ADK.
boolean
default:"true"
Capture v2 is on by default (SDK 1.6.0 and later). It records tool results (redacted JSON, up to 32,768 characters, with result size and emptiness), tool definitions, the structured messages and system prompt a model saw, requested tool calls, the output schema and agent input and output text, each with a content-free zs_capture marker saying how the field was captured. Redaction stays always on, and storePrompts and storeToolIO still decide what is stored: with either off, that content is not recorded and the marker says off.Turn it off with captureV2: false or the environment variable ZESPAN_CAPTURE_V2=off. The environment variable wins over the option, and ZESPAN_CAPTURE_V2=on forces it on. With it off, events are exactly what earlier versions sent. A one-time notice is logged on the first event when it is on only because of the default; it never includes event content, and setting the option or the variable explicitly silences it. Image metadata (captureImageMeta) is a separate opt-in and stays off by default; captureToolDefinitions and captureAgentIO stay on. Events can be larger, and requests are capped at about 900 KB, so tool-heavy apps send more requests. See Capture coverage.
number
default:"1.0"
Fraction of events to send, between 0.0 and 1.0. Set to 0.1 to trace 10% of calls.
string[]
Keys whose values are redacted before any data is stored. Passing this option replaces the default list — it is not merged. Applied regardless of storePrompts.
boolean
default:"false"
Needs capture v2, which is on by default. For each image in a model request, records only metadata on the model call (also a failed one): the declared type, the type read from the first bytes, the size, whether the base64 text is valid, width and height for PNG, JPEG, GIF and WebP, the URL scheme and host (never the path or query), whether a pre-signed link had already expired, a SHA-256 for images up to 256 KiB, and the tool call that produced the image. It never records the image itself. With storePrompts off the host and the hash are left out. Also on with ZESPAN_CAPTURE_IMAGE_META=1; ZESPAN_CAPTURE_IMAGE_META=0 always turns it off. See Capture fields.
boolean
default:"true"
On by default (SDK 1.6.0 and later); pass false to turn it off. Replaces emails, phone numbers, cards, IBANs, SSNs, national ids, IPs, street addresses and dates of birth with keyed tokens such as [EMAIL_3F9A2C1B] inside your process, before the event is sent. Secrets and sensitive keys are always removed, and the server also redacts content fields (identity fields such as user_id are stored as sent). See PII redaction.
string
Secret that keys the PII tokens. Default: a random key per process, so tokens compare only inside that process. Set the same value in every service of a multi-service app so a trace that crosses services gets equal tokens. Use a random secret of 32 bytes or more, and never log it. The SDK never logs or sends it.
number
default:"50"
Number of events to accumulate before flushing.
number
default:"2000"
Milliseconds between automatic flushes. The SDK also flushes on process exit.
number
default:"10000"
Maximum number of events held in the in-memory queue. Once exceeded, the oldest queued event is dropped and counted. See Delivery, retries and lost events.
number
default:"20971520"
Maximum bytes of encoded events held in the in-memory queue (20 MiB). Whichever of maxQueueSize and maxQueueBytes is reached first drops the oldest event. An event whose size estimate is over this limit is dropped when queued (counted as too_large) without being trimmed. Values below 1,000,001 also drop an event whose size check could not finish.
number
default:"30000"
Timeout for each request to the ingest API, in milliseconds. Events from a timed-out request are kept and retried. See Delivery, retries and lost events.
number
default:"10000"
Total time the SDK spends delivering queued events when your process exits. Each request during that final flush times out after shutdownRequestTimeoutMs (default 5000, never more than requestTimeoutMs).
{ hosts: string[] }
default:"off"
For calls that go through an AI gateway which also exports its own traces to Zespan. For a client whose base URL host is listed (exact name, or a leading dot like .internal), the wrapped OpenAI, Anthropic and OpenRouter calls add traceparent (plus cf-aig-otel-trace-id and cf-aig-otel-parent-span-id), so a gateway that honours them nests its span under the SDK span and Zespan can count the call’s tokens and cost once (when merging is on, see the linked section). A header you set yourself is never replaced. Nothing is added unless you set this. Every LLM event of these wrappers also records the provider’s response id as gen_ai.response.id. See Using the SDK too.
string
default:"https://api.zespan.com"
Override the API base URL. Use this only for self-hosted deployments.
boolean
default:"false"
When true, also exports spans to an OpenTelemetry-compatible backend. Requires otelEndpoint.
string
OTLP collector endpoint. Required when enableOTel is true.
string
Service name attached to exported OpenTelemetry spans.
boolean
default:"false"
When true, logs internal errors to the console. Enable during integration testing.
boolean
default:"true"
When true (default), automatically instruments installed OpenAI, Anthropic, and Google (@google/generative-ai and @google/genai) clients as soon as they’re detected — no explicit wrapper call required. Set to false to trace only clients you wrap explicitly. See Automatic instrumentation.
string
Your Zespan project ID. Required to receive config propagation updates from ZespanPilot or the dashboard.
boolean
default:"true"
When false, disables config propagation even if projectId is set — the SDK will not fetch or apply remote config changes.

Provider wrappers

OpenAI

Anthropic

Google Generative AI

This covers the legacy @google/generative-ai package. For Google’s current SDK (@google/genai) — including image generation, embeddings, and Veo video generation — use zespan.wrapGoogleGenAI() instead. See Google Generative AI for the full reference.

AWS Bedrock

wrapBedrock patches both client.converse and client.converseStream, plus client.send() calls that dispatch a ConverseStreamCommand — streaming Bedrock calls are traced the same as non-streaming ones.

Mistral

Groq

OpenRouter

LiteLLM

Automatic instrumentation (autopatch)

By default, Zespan also patches installed OpenAI, Anthropic, and Google (@google/generative-ai, @google/genai) SDKs directly at the module level as soon as zespan.init() runs and the package is detected — no wrapOpenAI-style call required. This is useful for code paths where you can’t easily reach the client instantiation, such as third-party libraries that construct their own client internally.
Set autopatch: false at init to disable this and trace only clients you wrap explicitly. Framework integrations (LangChain, Google ADK) are not affected by this flag — they use their own callback-based instrumentation. If you’re writing a custom integration for a framework Zespan doesn’t support out of the box, call markFrameworkActive(spanId) before your handler’s LLM call fires and markFrameworkInactive(spanId) after, so autopatch doesn’t also enqueue an event for the same call and create a duplicate.

Framework integrations

Beyond direct provider wrappers, Zespan integrates with common agent frameworks. Each has a dedicated reference page with full setup instructions:
  • LangChain — ZespanCallbackHandler traces chains, agents, tools, and retrievers automatically.
  • Google ADK — instrumentADK, wrapADKRunner, and wrapADKAgent trace Agent Development Kit agents and multi-agent delegation.
  • Vercel AI SDK — instrumentVercelAI() traces generateText, streamText, generateObject, and tool calls via OpenTelemetry.
  • LlamaIndex — ZespanLlamaIndexHandler attaches to LlamaIndex’s callback manager to trace LLM calls, tool use, and agent steps.
  • CrewAI and AutoGen / AG2 — these frameworks are Python-native. The TypeScript SDK exports context-propagation helpers (extractAgentContext, attachTraceToAutoGenMessage, extractTraceFromAutoGenMessage) so a Node.js service receiving calls from a Python agent can continue the same trace.

Context enrichment

Use withZespanContext to attach a userId, sessionId, or custom tags to all traces generated within a function scope.
Set userId and sessionId on every request that involves a logged-in user. This enables per-user cost breakdown and session replay in the Zespan dashboard.
You can later erase or export everything Zespan holds for one end user by this userId or sessionId — see End-user data requests. Use a unique sessionId per conversation: erasing a user also removes every session they appeared in.

Agent tracing

Use withAgent to trace a multi-step agent workflow. It creates an agent span and exposes methods to log plans, trace tool calls, and record handoffs.
AgentContext methods:
  • agent.logPlan(steps: string[]) — records a planning span
  • agent.traceTool(name, args, fn) — wraps an async function, records args and result as a tool span
  • agent.delegateTo(targetName, reason) — records a handoff span

Manual spans

Use startSpan to instrument any non-LLM operation (retrieval, embeddings, custom model calls) as a custom span and attach evaluation scores. It returns a span handle and a run helper for propagating context to nested calls.
span.end(options) requires a status ("success", "error", "timeout", "rate_limited", or "cancelled") and is async — always await it. See Manual spans for the full option reference and a complete RAG pipeline example.

Prompt management

The PromptClient fetches versioned prompts from the Zespan prompt library at runtime. Results are cached locally.
See Prompt management for the full API reference — get, list, create, updateLabels, compile, clearCache.

Dataset experiment runs

The client.datasets client lets you run your own pipeline against a Zespan dataset and link the results back by trace ID — then score and compare runs in the dashboard. Fetch a dataset’s items, run each one through your own code however you like, and link each item to a run using the trace ID your own tracing already produced.

datasets.getItems(datasetName)

Lists all of a dataset’s items by dataset name, fetching page after page until the last one. Returns Promise<DatasetItem[]> — each item has id, input, expectedOutput, and metadata. It stops with a warning after 100 pages; for very large datasets, use iterateItems instead.
string
required
Name of the dataset to fetch items from.

datasets.iterateItems(datasetName)

An async iterator over a dataset’s items that fetches one page at a time, so you can process large datasets without holding every item in memory:

datasets.createRun(datasetName, runName, options?)

Creates or fetches a named run against a dataset — idempotent, so it’s safe to call every time a job starts. Returns Promise<DatasetRunHandle>.
string
required
Name of the dataset to run against.
string
required
Name for this run, e.g. "gpt-4o-v2". Calling createRun again with the same name returns the existing run instead of creating a duplicate.
string
Optional human-readable description of what this run is testing.
Links a dataset item to the run via a trace ID your own code already produced. Returns Promise<void>.
string
required
id of the dataset item being evaluated, from getItems.
string
required
Trace ID that your pipeline produced for this item — for example, an ID you generated and passed into withZespanContext.
string
Optional span/observation ID, if you want to link to a specific span within the trace rather than the trace as a whole.
Once results are linked, head to Datasets in the dashboard to score and compare runs against each other.

Report changes

zespan.change(...) tells Zespan that a deploy, prompt, model, config, flag or dependency changed. It sends one small request with metadata only (kind, service, environment, a short ref, a version label, a time and attributes you choose) and never throws: it returns { ok, id, replayed, reason }.
It reads ZESPAN_CHANGE_KEY (a write-only change key) first, then your SDK key, so a CI secret can only report changes. ZESPAN_CHANGES=off sends nothing. Reporting the same release twice stores one change. See Report changes from your CI for every field, the limits and the CLI and pipeline snippets.

PII redaction

Zespan automatically redacts values from tags and metadata fields (and stored prompt/completion text) before they leave your application. The key is preserved; the value is replaced with "[REDACTED]". Default redacted keys (applied when redactKeys is not set): password, secret, token, api_key. Passing your own redactKeys array replaces the default list — it does not merge with it. Include any of the defaults you still want to keep:
Redaction applies to tags and metadata fields, and to stored prompt and completion text. Prompt storage is on by default — set storePrompts: false to disable it entirely.
Pattern-based detection of PII that doesn’t live under a known key name — emails, phone numbers, SSNs, credit card numbers embedded in free text — is on by default: matches become keyed tokens such as [EMAIL_3F9A2C1B] before the event leaves your process. Pass redactPii: false to turn it off. See PII redaction for tokens, redactionKey, the CPU budget and the 32,768-character tool payload cap.

Guardrails

Guardrails run content safety checks before sending a prompt to the LLM (pre-check) and before returning the completion (post-check). Pass guardrails: true to any wrapper to enable both phases.
See Guardrails for the full reference — parameter defaults, all fields, the direct checkGuardrails() API, and a multi-wrapper example.

Config propagation

Zespan can push configuration changes — model overrides, sample rate, guardrail toggles — to your running application without a redeployment, as long as projectId is set at init (see Init options). Changes made via ZespanPilot or the dashboard take effect within the next flush cycle (default 2 seconds). See Config propagation for the full list of rule types — including caching, retries, timeouts, fallbacks, and A/B tests — and the programmatic ConfigClient API.

Delivery behaviour

The SDK sends events in the background and retries on failure, so a slow or briefly unavailable API doesn’t lose your data or slow your application:
  • No duplicates on retry. Each batch is sent with an automatic Idempotency-Key that is reused on every retry, so a lost response can’t ingest the same events twice.
  • At most 100 events per request. Larger backlogs are sent as several requests, and a batch the server rejects as too large is split in half.
  • Timeouts. Requests time out after 30 seconds (requestTimeoutMs). Events from a timed-out request are kept and retried.
  • Retry-After is honoured, up to 60 seconds. A persistent 429 or 503 means your events are being kept and retried, not lost.
  • Bounded queue. While delivery is failing, events wait in memory, up to 10,000 events. When the queue is full, the oldest events are dropped.
  • Unacceptable batches are dropped. A batch the server will never accept — for example, one with no valid events — is dropped with an error log so it doesn’t block newer events. A 401 or 403 keeps your events and backs off.
  • Safe serialisation. BigInt values become strings, circular references become [Circular], and NaN and infinite numbers become null. An event that still can’t be serialised is dropped on its own.
  • Exit flush. When your process exits, the SDK makes one delivery attempt per batch within a 10-second total budget (shutdownBudgetMs). Events not delivered in that time are dropped, so call flush() before exiting when you need delivery guarantees.
In one rare case a duplicate is still possible: when the queue overflows while a request is in flight and that request then fails.

Flushing in serverless environments

In short-lived processes such as AWS Lambda, Vercel Functions, or Cloudflare Workers, call zespan.getClient().flush() explicitly before the handler returns to guarantee delivery.
Omitting zespan.getClient().flush() in serverless environments is the most common cause of missing traces. Always call it before your handler returns.

Delivery, retries and lost events

The SDK sends events in the background and is built so that a slow or failing Zespan API never blocks or breaks your app. These are the rules it follows. They are the same in the TypeScript and Python SDKs (TypeScript 1.6.0 and later).
An event whose size estimate is over maxQueueBytes is dropped when queued, without trimming. Setting maxQueueBytes below 1,000,001 also drops events whose size check could not finish (an extremely large or deeply nested value), because those are estimated at 1,000,001 bytes or more. Leave it at the default unless you need a smaller memory limit.

Lost-event reports

Every dropped event is counted by reason. The SDK sends those counts, and nothing else, to POST /v1/sdk/client-reports, so Zespan can show you how many events were lost in your app before they arrived (see Events lost in the app). A report holds a random report id, the SDK name and version, and a list of {reason, events} counts. It never contains event content, trace ids, hostnames or error messages.
  • Reports start after the first successful send. When events were lost, a report goes out about once a minute (a 429 with Retry-After can change the next attempt). An empty heartbeat goes out once an hour, so the dashboard can tell “none lost” from “no report received”.
  • A report that fails is retried with the same id, so it is never counted twice; one still undelivered after 55 minutes is dropped. A failing report is not counted as a lost event and does not stop events from being sent.
  • A self-hosted API without the route answers 404; the SDK then tries again only at the hourly cadence.
  • The report request has a 5 second timeout and can hold the process open up to 5 seconds at exit.

Next steps

Instrumentation

See which inputs your traces recorded, and the step that fixes any that did not.

Events lost in the app

How lost-event counts reach the dashboard.

PII redaction

Keyed tokens, sharing a key across services, and the 32,768-character tool payload cap.