# JavaScript SDK API

> Functions, classes, options, parameters, and return values for the Anectico JavaScript and TypeScript SDK.

Canonical page: https://anectico.com/docs/reference/javascript-sdk/


This is the application-developer API for `@anectico/sdk` 0.1.x. Import optional capabilities from
their documented subpaths so replay, React, and provider-specific code stays out of the base bundle.

Use a key with `ingest:write` for traces, metrics, logs, and errors. Add `analytics:write` for
identity, groups, diagnostic events, and flag exposure events; `flags:read` for decisions; and
`replay:write` for replay.

## Initialize the core client

```typescript
import { init, AnecticoClient } from '@anectico/sdk';

const anectico = await init({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'orders-api',
});

// Equivalent lifecycle with explicit construction:
const second = await new AnecticoClient(options).start();
await second.stop();
```

| API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `init(options?)` | `AnecticoOptions` | `Promise<AnecticoClient>` | Creates, validates, starts, and returns one client. Recommended entry point. |
| `new AnecticoClient(options?)` | `AnecticoOptions` | `AnecticoClient` | Creates an unstarted client. Call `start()` before recording. |
| `start()` | — | `Promise<AnecticoClient>` | Validates configuration and starts OTLP transports. Repeated calls return the same client. |
| `flush()` | — | `Promise<void>` | Waits for in-flight identity posts and force-flushes trace, metric, and log providers without stopping them. Rejects on exporter failure. Concurrent calls share one flush. |
| `stop()` | — | `Promise<void>` | Waits for bounded in-flight `identify`/`group` posts, flushes transports, removes installed hooks, and releases resources. Rejects on exporter failure; concurrent and repeated calls share the same shutdown result. |
| `registerShutdownHook(hook)` | cleanup function | `void` | Registers a cleanup that `stop()` invokes once. Framework integrations use this to remove installed handlers. |
| `isRunning()` | — | `boolean` | Reports whether the client currently accepts telemetry. |
| `getConfig()` | — | `ResolvedConfig` | Returns a defensive copy of the resolved configuration for diagnostics. It includes sensitive headers; do not log it. |
| `health()` | — | `HealthStatus` | Returns `healthy`, `degraded`, or `stopped`, recent internal errors, uptime, and signal counters. |
| `stats()` | — | `ClientStats` | Returns processed, dropped, and failed-export counters plus timing data. |

Keep one client per application process or browser page. Always await `stop()` during graceful
server shutdown so identity posts and buffered telemetry finish before the process exits. A rejected
identity endpoint response is reported through SDK debug logging instead of being treated as an
accepted delivery. In managed mode, allow application-drain time plus up to twice
`shutdownTimeout`: one deadline for identity/group delivery and one for concurrent trace, metric,
and log provider shutdown.

Node cannot intercept `SIGKILL`, `kill -9`, a forced zero-grace pod deletion, or host loss, so these
paths produce no terminal capture and run no final flush. The unexported tail can include active
spans, in-flight identity/group requests, up to `maxQueueSize` ended spans and logs per batched
signal (2,048 each by default), and metric observations since the last
`metricExportInterval`. Prefer `SIGTERM` with a non-zero termination grace period.

For AWS Lambda-style runtimes, memoize one `initNode()` promise at module scope and await `flush()`
in the handler's `finally` block. This exports the completed invocation before the runtime can freeze
without disabling warm reuse. Keep end-user attribution request-scoped with
`contextWithDistinctId`; reserve `stop()` for final process teardown.

### Browser performance budget

For production browser applications, keep the core SDK's incremental compressed transfer at or
below 250 KiB and load the optional replay subpath only when recording is enabled; its incremental
compressed transfer budget is 200 KiB. Against an otherwise identical control build, target no more
than a 20% matched-workload regression, no more than 200 ms total blocking time, no task above
100 ms, and no SDK request amplification above 5% of application requests. These are acceptance
ceilings, not expected steady-state values. Measure cold and warm builds on representative long
pages and interaction bursts, and call `stop()` when instrumentation is disabled so hooks, timers,
and observers are released.

Replay uses ordinary fetches for size- and timer-triggered chunks so large initial snapshots and
short activity bursts are not rejected by the browser's shared keepalive-body quota. The final
`pagehide` tail alone uses a keepalive request because it may need to outlive the document.

## `AnecticoOptions`

Queue sizes and stack-frame limits must be safe integers; timeouts must be finite and positive.
Endpoints must be absolute HTTP(S) URLs without credentials, query strings, or fragments. An
explicitly empty service name is invalid even when the environment contains a name. Authentication
headers override custom headers case-insensitively.

Explicit options override environment values. Required values have no usable default.

| Option | Type | Default/environment | Purpose |
| --- | --- | --- | --- |
| `apiKey` | `string` | `ANECTICO_API_KEY`; required | Project-scoped Anectico API key. |
| `serviceName` | `string` | `OTEL_SERVICE_NAME`; required | Logical application or service name. |
| `serviceVersion` | `string` | `OTEL_SERVICE_VERSION`; `0.0.0` | Deployed version. |
| `environment` | `string` | `ANECTICO_ENVIRONMENT`; runtime-derived | Deployment environment such as `production`. |
| `release` | `string` | `ANECTICO_RELEASE`, then real `serviceVersion` | Release used for regression detection and source maps. |
| `dist` | `string` | `ANECTICO_DIST`; empty | Artifact/build discriminator within a release. |
| `endpoint` | `string` | `ANECTICO_ENDPOINT`; `https://api.anectico.com` | Base API and OTLP URL. |
| `protocol` | `'http'` | `http` | JavaScript supports OTLP over HTTP only. |
| `openTelemetryMode` | `'managed' \| 'existing'` | `ANECTICO_OTEL_MODE`; `managed` | Use `existing` when the application already owns global providers, exporters, instrumentation, and their lifecycle. |
| `enableTraces` | `boolean` | `ANECTICO_ENABLE_TRACES`; `true` | Enable trace export. |
| `enableMetrics` | `boolean` | `ANECTICO_ENABLE_METRICS`; `true` | Enable metric export. |
| `enableLogs` | `boolean` | `ANECTICO_ENABLE_LOGS`; `true` | Enable log export. |
| `enableErrorCapture` | `boolean` | `ANECTICO_ENABLE_ERROR_CAPTURE`; `true` | Enable `captureError` and `captureMessage`. |
| `enableFetchTracing` | `boolean` | `true` | Browser only: instrument fetch/XHR and inject trace headers. Disabling it does both. |
| `propagateTraceHeaderCorsUrls` | `(string \| RegExp)[]` | empty | Browser cross-origin destinations allowed to receive trace and identity headers. |
| `propagateTraceHeaderUrls` | `(string \| RegExp)[]` | `ANECTICO_PROPAGATE_TRACE_HEADER_URLS`; empty | Node HTTP(S) destinations allowed to receive trace and identity headers. |
| `ignoreIncomingRequestPaths` | `string[]` | `ANECTICO_IGNORE_INCOMING_REQUEST_PATHS`; empty | Node managed mode: exact or trailing-`*` paths excluded from generic incoming HTTP spans, such as health and internal control routes. |
| `traceSampleRate` | `number` | `OTEL_TRACES_SAMPLER_ARG`; `0.1` production, `1` development | Trace sampling in the inclusive range 0–1. |
| `errorSampleRate` | `number` | `ANECTICO_ERROR_SAMPLE_RATE`; `1` | Non-fatal captured-error sampling in the inclusive range 0–1. |
| `attachStackTrace` | `boolean` | `ANECTICO_ATTACH_STACK_TRACE`; `true` | Parse and attach error stack frames. |
| `maxStackTraceFrames` | `number` | `ANECTICO_MAX_STACK_TRACE_FRAMES`; `50` | Maximum captured frames. |
| `batchSize` | `number` | `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`; `512` | Maximum items per export batch. |
| `batchTimeout` | `number` | `OTEL_BSP_SCHEDULE_DELAY`; `5000` ms | Maximum delay before a trace/log batch flushes. |
| `maxQueueSize` | `number` | `OTEL_BSP_MAX_QUEUE_SIZE`; `2048` | Queue capacity before signals are dropped. Must be at least `batchSize`. |
| `exportTimeout` | `number` | `OTEL_BSP_EXPORT_TIMEOUT`; `30000` ms | Per-export deadline. |
| `metricExportInterval` | `number` | `OTEL_METRIC_EXPORT_INTERVAL`; `60000` ms | Metric push interval; raised to at least `exportTimeout`. |
| `shutdownTimeout` | `number` | `OTEL_BSP_SHUTDOWN_TIMEOUT`; `5000` ms | Per-phase graceful deadline: bounds one-shot identity/group delivery and the concurrent provider-shutdown phase. |
| `resourceAttributes` | `Record<string,string>` | empty | Extra OpenTelemetry resource attributes. |
| `headers` | `Record<string,string>` | empty | Extra headers on exports. Do not expose secrets in browser builds. |
| `debug` | `boolean` | `ANECTICO_DEBUG`; `false` | Enable SDK diagnostic logs. |

In `existing` mode, Anectico creates no provider, exporter, propagator, or auto-instrumentation and does
not flush or shut down application-owned providers. Start the application's OpenTelemetry SDK
first. Anectico's manual APIs use its registered globals; the application remains responsible for
provider flush/shutdown and for installing both W3C Trace Context and W3C Baggage propagation.
Add the exported `BaggageSpanProcessor` and `BaggageLogRecordProcessor` to application-owned
providers before startup when request-scoped identity should be materialized on spans and logs.

## Traces, metrics, logs, and AI operations

| Method | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `tracer` | property | OpenTelemetry `Tracer` | Access the underlying tracer for advanced instrumentation. |
| `startSpan(name, options?)` | name; OTel `SpanOptions` | OTel `Span` | Starts an inactive span; call `span.end()`. To parent subsequent operations, use `client.tracer.startActiveSpan()` with a context manager (`initNode()` installs one in managed Node mode). Returns a no-op span before startup. |
| `recordMetric(name, value, labels?)` | metric name; number; string labels | `void` | Records one histogram observation. Invalid calls increment dropped-metric stats rather than throwing. |
| `logEvent(level, message, attrs?)` | `debug \| info \| warn \| error \| silent`; message; attributes | `void` | Emits one structured OTel log. Complex attribute values are JSON-stringified. |
| `recordLLMCall(options)` | `LLMCallOptions` | `void` | Records one completed model call as a `gen_ai` client span. |
| `recordToolCall(options)` | `ToolCallOptions` | `void` | Records one completed agent tool/function call. |
| `startAgentRun(options)` | `AgentRunOptions` | `{ span, context, end }` | Starts an `invoke_agent` span. Run child work in the returned OTel context, then call `run.end(status, reasonCode)`; the first valid outcome wins. |

Agent-run status is `completed`, `failed`, `timed_out`, `cancelled`, or `max_steps`. The reason is a
lowercase code matching `[a-z][a-z0-9_]{0,63}`; free text is rejected. Calling `run.span.end()`
directly bypasses the terminal contract and produces only the legacy inferred status.

`LLMCallOptions.model` is required. Optional fields are `responseModel`, `provider`, `operation`,
token counts (`inputTokens`, `outputTokens`, `reasoningTokens`, `cacheReadTokens`,
`cacheWriteTokens`), `costUsd`, `finishReason`, `startTime`, `endTime`, and `isError`. `messages` and
`output` opt into prompt/completion content capture.

`ToolCallOptions.name` is required. Optional fields are `type`, `callId`, `conversationId`,
`agentName`, `isError`, `arguments`, and `result`. Arguments and results can contain sensitive data;
omit them unless content capture is approved.

`AgentRunOptions.agentVersion` is optional. Set it to the release label you deploy. The SDK records
it as the standard `gen_ai.agent.version` span attribute; it does not reuse `service.version`, because
the hosting service and the agent can be released independently.

### Context assembly events

The run handle exposes `recordContextAssembly(value)`, `recordContextCompaction(value)`, and
`recordContextCache(value)`. Call them inside `context.with(...)` while a recording model span is
current and carries `gen_ai.operation.name = chat | text_completion | generate_content |
embeddings | fetch_response`. The SDK throws for the run span, a tool span, an ended span, or no
active span; it validates every field before adding the one event, so a rejected value records
nothing.

```typescript
const run = client.startAgentRun({ agent: "support-planner" });
await context.with(run.context, async () => {
  const model = client.tracer.startSpan("chat gpt-4o", {
    kind: SpanKind.CLIENT,
    attributes: { "gen_ai.operation.name": "chat" },
  });
  await context.with(trace.setSpan(context.active(), model), async () => {
    run.recordContextAssembly({
      budgetTokens: 8192,
      reservedOutputTokens: 1024,
      assembledTokens: 6144,
      overflow: "none",
      assemblySha256,
      sources: [{ kind: "memory", id: "memory:item-7", sensitivity: "confidential",
        tokens: 512, position: 3, visibility: "included" }],
    });
  });
  model.end();
});
run.end("completed");
```

See [Context events](/docs/reference/agent-attributes#context-events) for exact bounds. Never send compaction
ratio; the server derives it. OpenAI and Anthropic wrappers add cache hit/miss events when cache
fields are present. A reported zero is a miss, while an absent field produces no event.

## Errors, messages, users, and breadcrumbs

Breadcrumbs snapshot nested JSON data when added. If optional data cannot be serialized, the
breadcrumb is retained without that data so error reporting can still include the trail.

| Method | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `captureError(error, options?)` | `Error`; `CaptureOptions` | `string` | Sends an error span and returns its generated ID. Returns `''` when capture is unavailable or sampled out. |
| `captureMessage(message, level?, options?)` | message; `ErrorLevel`; `CaptureOptions` | `string` | Captures a diagnostic message. Only `error` and `fatal` produce error status. |
| `addBreadcrumb(category, message, options?)` | strings; `BreadcrumbOptions` | `void` | Buffers context for the next captured error, then clears the buffer. Capacity is 100. |
| `setUser(user)` | `AnecticoUser` | `void` | Sets global error user context. Prefer `identify` for the cross-signal identity spine. |
| `clearUser()` | — | `void` | Removes global error user context. |

`CaptureOptions` accepts `tags: Record<string,string>`, `extra: Record<string,unknown>`, `user`,
`stackTrace`, `level`, and `fingerprint`. A fingerprint is a string or string array; commas are the
component delimiter, and `{{default}}` includes automatic grouping. Tag keys are encoded as indexed
`anectico.tag.*` telemetry attributes automatically; the capture level is indexed as
`error.level` for Issue filtering and context.

`AnecticoUser` accepts `id`, `email`, `username`, `ipAddress`, `segment`, and `data`. Breadcrumb options
accept `level` and `data`.

## Identity and accounts

Logout rotates the live anonymous identity and session even when browser storage cannot be
updated. Session storage failures fall back to stable memory state; a clock moving backward also
starts a fresh session. Rotating sessions releases their previous ownership markers.

| Method | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `identify(distinctId, properties?)` | stable ID; person properties | `void` | Switches shared identity and starts a best-effort identify request that `stop()` waits for. Empty IDs are ignored. |
| `group(groupType, groupKey, properties?)` | group type; stable key; group properties | `void` | Associates the current person with an account and starts a `$groupidentify` request that `stop()` waits for. |
| `reset()` | — | `void` | Logout: stops active replay recorders and flushes their prior-user tail before rotating anonymous identity/session; also clears groups, global error-user context, and uncaptured breadcrumbs. Other already-captured telemetry remains queued with its original attribution. |
| `getSessionId()` | — | `string` | Returns the current browser tab's cross-signal session ID, rotating it after its idle/max duration. |

For concurrent Node requests, use `contextWithDistinctId(ctx, id)` and
`distinctIdFromContext(ctx)` instead of changing process-wide identity. `adoptDistinctIdFromBaggage`
must be used only after an authenticated gateway has replaced untrusted inbound baggage.

Browser session state is tab-scoped. A reload keeps the same session, while a separate, duplicated,
or opener-created tab receives its own session and replay boundary. Call `identify` after
authentication in every tab. Calling `reset` on logout rotates only that tab's identity/session and
does not move another live tab's telemetry into the new customer's session. Replay's next chunk
index is stored beside this tab-scoped state so a reload appends to the recording instead of
replacing its first chunks.

## Diagnostic events: `@anectico/sdk/analytics`

Capture snapshots event properties when enqueueing. Later mutations do not change queued events.
Properties must be JSON-serializable; invalid events are rejected before they enter the queue.
The built-in analytics and feature-flag HTTP clients do not follow redirects: configure the final
API endpoint directly so project credentials stay at that endpoint.

```typescript
import { AnalyticsClient } from '@anectico/sdk/analytics';

const events = new AnalyticsClient({
  endpoint: 'https://api.anectico.com',
  apiKey: process.env.ANECTICO_API_KEY!,
});
```

`release` and `appVersion` are stamped as the reserved `$release`/`$app_version` string
properties on every `capture()`'d and `page()`'d event when configured, never invented when
they are not. An explicit option wins; otherwise Node falls back to
`process.env.ANECTICO_RELEASE`/`ANECTICO_APP_VERSION`, and a browser falls back to
`<meta name="anectico-release" content="…">`/`<meta name="anectico-app-version" content="…">`
tags your build tooling injects. See
[Reserved event properties](/docs/investigate/event-schema#reserved-properties).

| API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `new AnalyticsClient(options)` | endpoint, API key, optional `flushAt`, `flushInterval`, `requestTimeoutMs`, `fetchImpl`, `identity`, `onDeliveryError`, `release`, `appVersion` | `AnalyticsClient` | Creates a batching event client. Defaults: flush at 20 events or 5 seconds; 10-second request timeout. |
| `getDistinctId()` | — | `string` | Current shared known or anonymous ID. |
| `capture(event, properties?)` | name; properties | `void` | Queues one product event with identity, groups, session, timestamp, and dedup ID. |
| `page(name, properties?)` | explicit page name; optional properties | `void` | Queues `$pageview` with `$page_name`, using the same capture identity, consent and retry behavior. Does not collect browser URL, title or referrer. |
| `identify(distinctId, personProps?)` | ID; person properties | `void` | Queues identity merge, switches identity, and starts a flush. |
| `group(type, key, properties?)` | group type/key; properties | `void` | Records membership and a group identify event. |
| `reset()` | — | `void` | Rotates anonymous identity and session. |
| `flush({ keepalive? }?)` | optional browser keepalive | `Promise<boolean>` | Validates the complete indexed acknowledgement for each chunk. Resolves `true` only when every attempted event was queue-acknowledged or the queue was empty; `false` when explicit refusals were dropped without pending retries; rejects while uncertain/retryable events remain. |
| `stop({ keepalive? }?)` | optional browser keepalive | `Promise<boolean>` | Stops timers/listeners and flushes remaining events, resolving what that final flush resolved. Rejects while retryable events remain queued. Further capture calls fail. |
| `disable()` | — | `Promise<void>` | Immediately blocks new capture/page/identify/group calls, removes delivery triggers and discards pending analytics. Waits for already-dispatched delivery to settle without retrying uncertain events. Terminal for this instance. |
| `stats()` | — | `{ recorded, delivered, dropped, queued }` | Delivery counters. `delivered` counts validated queue acknowledgements, `dropped` counts explicit refusals/local discards including withdrawal, and `queued` includes events awaiting an acknowledgement or retry. |

Page tracking is opt-in: call `events.page('Pricing', { plan: 'free' })` when your application
decides a page view occurred, including after a client-side route change. The SDK does not
automatically observe navigation or read browser URL, title or referrer. The explicit name
overrides a `$page_name` supplied in properties. Choose stable names and pass only properties
approved for collection; arbitrary supplied properties are not automatically redacted.

Capture uses the [versioned indexed acknowledgement contract](/docs/reference/rest-api#capture-acknowledgements).
HTTP 200 alone never establishes delivery. The SDK validates the version, request UUID, original
indices, all outcomes and totals before updating counters. Queue acknowledgement does not prove
unique stored rows, query visibility or completed identity changes.

`flush()` resolves `false` for explicit refusals, including invalid events or quota refusal. It
rejects if uncertain events remain, including network failures, malformed responses at any HTTP
status, and typed retryable request failures. A single flush can contain both dropped events and
an uncertain remainder: inspect `stats()` and `onDeliveryError` even when the promise rejects.
Accepted and refused siblings are never automatically retried. Uncertain events retain their
original message ID, identity, timestamp and payload; unsent later chunks also remain queued.
Retries wait at least one second and honor `Retry-After` up to 60 seconds.
`requestTimeoutMs` defaults to 10,000 ms and must be positive; request dispatch and receipt
reading each have a bounded deadline.

For consent withdrawal, use `await events.disable()` instead of a final-flush `stop()`.
It immediately prevents new `capture`, `page`, `identify` and `group` calls from collecting data or
changing identity, removes timer/page-exit delivery, and reports pending data as local
`collection_disabled` drops. Already-dispatched requests may still be committed: valid
acknowledgements retain their normal counters, while uncertain positions and unsent later
chunks are discarded without retry. A concurrent flush resolves `false` if it discards work;
an empty flush after disable performs no network activity. After disable settles, queued is zero.
Repeated disable is safe; new consent requires a new AnalyticsClient. Reset is logout, not
consent withdrawal. This controls only this analytics instance; separately stop replay and
manage the other telemetry clients you initialized. Do not initialize collection before consent
where your application requires it.

Refusals are reported once per affected chunk through `onDeliveryError({ status, dropped, reason })`,
`stats().dropped`, and `console.warn`. Reasons include `invalid_event`, `quota_exceeded`,
`mixed_rejection`, `bad_request`, `unauthorized`, `forbidden`, `payload_too_large`, and
`queue_overflow` and `collection_disabled` (local discards use status 0). Unknown delivery alone does not fire a drop
callback. A callback that throws is swallowed. A malformed or oversized acknowledgement never
counts as accepted or definitely refused; response reads are bounded to 64 KiB and a deadline.

```ts
const events = new AnalyticsClient({
  endpoint: 'https://api.anectico.com',
  apiKey: process.env.ANECTICO_API_KEY!,
  onDeliveryError: ({ status, dropped, reason }) => {
    myAlerting.warn(`anectico dropped ${dropped} events: ${status} ${reason}`);
  },
});
```

Explicit `$groups` in event properties override the current shared memberships. A membership
change takes effect only after its event is successfully serialized and queued.
Capture receipts distinguish accepted events from validation or quota drops, including partial
acceptance in an HTTP 200 response. Counters and the delivery-error callback report only the
rejected portion as dropped, and `flush()` resolves `false` for that partial outcome.

Analytics and feature-flag endpoints must be absolute HTTP(S) base URLs without embedded credentials,
query strings, or fragments. Failed flag-exposure delivery can retry on the next flag read; it never
interrupts evaluation.

The diagnostic-event queue is bounded and **in memory**. A transient network failure is requeued
with the same message ID and the timer retries while the page remains alive, so reconnecting without
navigation preserves order and backend deduplication. A reload, navigation, browser termination, or
OS process kill replaces that memory and can discard events that have not reached Anectico. Call
`await events.flush({ keepalive: true })` before controlled navigation when delivery matters; do not
describe browser diagnostic events as durable offline storage.

## Feature flags: `@anectico/sdk/analytics`

Bootstrap data and returned payload containers are copied. Exposure deduplication distinguishes
identity, flag name, and the response's type and value. Remote decision responses must contain one
JSON document of at most 16 MiB, with boolean or string flag values. Invalid responses preserve the
last valid cache; overlapping reloads allow only the latest invocation to replace it.

`FlagsClient` needs `flags:read`; an Anectico exposure sender needs `analytics:write`.
Use `endpoint: 'https://app.anectico.com'` for flag decisions. The default exposure sender uses
that same endpoint, but hosted capture belongs on `https://api.anectico.com`. Supply an explicit
capture callback backed by `AnalyticsClient` on the ingestion host, as shown in
[Check rollout health](/docs/investigate/rollout-health#create-and-evaluate-a-flag). Keep the
customer identity in that event client consistent with the ID passed to `reload`.

| API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `new FlagsClient(options, capture?)` | endpoint, API key, optional fetch/bootstrap; optional exposure callback | `FlagsClient` | Creates an in-memory decision client. Bootstrap avoids the first network call. |
| `reload(distinctId, personProperties?, groups?)` | person ID; properties; group map | `Promise<void>` | Calls `/api/v1/decide`, replaces cached flags/payloads, and resets exposure dedup when identity changes. |
| `getFeatureFlag(key)` | flag key | `boolean \| string \| undefined` | Returns a decision and emits one deduplicated exposure for that key/value. |
| `isFeatureEnabled(key)` | flag key | `boolean` | True for `true` or a non-empty string variant. |
| `getFeatureFlagPayload(key)` | flag key | `unknown` | Returns the payload from the latest decision. |
| `allFlags()` | — | `Record<string, boolean \| string>` | Returns a copy of every cached decision. |
| `hadEvaluationErrors()` | — | `boolean` | Reports whether the server used a fallback during the latest evaluation. |
| `reset()` | — | `void` | Clears only the per-key/value exposure dedup set. Cached decisions and payloads remain available until the next `reload`. |

Browser decisions reveal flag configuration to the client. Never store secrets in flag rules or
payloads.

For Node/server local evaluation, import `LocalFlagsClient` from `@anectico/sdk/flags-local` and pass
`{endpoint, apiKey, projectId}` plus an optional capture callback in the same shape accepted by
`FlagsClient`. `refresh()` validates a project-bound snapshot with `ETag`/`If-None-Match`; repeated
refreshes are serialized. `evaluate(key, distinctId, personProperties?)` returns
`{value?,payload?,snapshot_version?,reason,error?}` and never returns a value from a stale snapshot
or an unsupported cohort/group target. Successful `matched`/`default` reads emit
`$feature_flag_called` once per identity/key/value when a callback is configured; call
`reset()` after an explicit identity reset. A network-backed callback also needs `analytics:write`.

| Local API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `new LocalFlagsClient(options, capture?)` | `options`: endpoint, API key, canonical project ID, optional fetch/clock; `capture(event, properties, distinctId)` | `LocalFlagsClient` | Creates one exact-project cache. Snapshot reads need `flags:read`; an Anectico-backed capture callback needs `analytics:write`. |
| `refresh()` | — | `Promise<"updated" \| "not_modified">` | Fetches or conditionally revalidates the strict, bounded snapshot without replacing a last-known-good cache on failure. |
| `evaluate(key, distinctId, personProperties?)` | flag key, exact identity, person properties | `LocalEvaluationResult` | Evaluates only a fresh snapshot and emits a successful exposure through the optional callback. |
| `reset()` | — | `void` | Clears local `(identity,key,value)` exposure deduplication; preserves the snapshot. |

## Replay: `@anectico/sdk/replay`

`startReplay(options)` starts rrweb recording and returns a synchronous stop function. The stop
function removes hooks and flushes the buffered tail. It is idempotent.

`AnecticoClient.reset()` also stops every active recorder synchronously and flushes its buffered tail
before changing the shared identity/session. This prevents a recorder started for user A from
capturing user B's UI after logout. Start a new recorder after identifying user B if replay should
resume.

Replay chunks are best-effort and buffered only in page memory. A failed chunk upload is dropped
rather than retried, and an offline reload can lose the buffered tail. Replay must never be used as a
durable audit log.

| Option | Type/default | Purpose |
| --- | --- | --- |
| `endpoint`, `apiKey` | required strings | Replay endpoint and key with `replay:write`. |
| `sessionId`, `distinctId` | optional strings | Override shared session/person linkage. |
| `maxEvents` | number; `50` | Flush threshold. |
| `flushMs` | number; `5000` | Time-based flush interval. |
| `maskAllInputs` | boolean; `true` | Mask input values in the DOM recording. |
| `slimDOM` | `true \| 'all'`; unset | Remove non-visual scripts, comments, and head metadata from DOM snapshots. Use `'all'` for the smallest initial snapshot. |
| `inlineStylesheet` | boolean; rrweb default `true` | Inline linked stylesheets into snapshots. Set `false` when the replay viewer can load the original stylesheets and snapshot size matters. |
| `captureConsole` | boolean; `true` | Record console events. Disable unless required. |
| `captureNetwork` | boolean; `true` | Record fetch/XHR metadata and bodies. Disable unless required. |
| `maxBodyBytes` | number; `10240` | Per-body truncation limit. |
| `maxNetworkEvents`, `maxConsoleEvents` | number; `1000` | Per-session event caps. |
| `fetchImpl` | `typeof fetch` | Test/SSR transport override. |

`shouldFlush(...)` and `buildChunkBody(...)` are exported pure helpers for custom/testing use; most
applications should call only `startReplay`.

Initialize replay before mounting a very large DOM so the first visible state is recorded, then
hydrate long feeds or tables progressively. For pages with hundreds of nodes, combine progressive
rendering with `slimDOM: 'all'` and, where the viewer can load the original CSS,
`inlineStylesheet: false`. This keeps recorder parse work and the initial snapshot off the longest
main-thread task without reducing the recorded user journey.

## Node, browser, Express, Fastify, NestJS/GraphQL/Prisma, Next.js, and React

| Import/API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `@anectico/sdk/node` `initNode(options?)` | `AnecticoOptions` | `Promise<AnecticoClient>` | Starts the client and installs Node HTTP/process handlers. |
| `registerProcessHandlers(client)` | client | cleanup function | Installs uncaught exception, rejection, and shutdown handlers. Fatal exceptions and rejections are printed to stderr before the best-effort telemetry flush. The first terminal event owns one capture/flush/exit sequence; capture failure or secondary terminal events cannot bypass or duplicate it. Anectico owns `SIGTERM`/`SIGINT` only when no application listener is present; an application listener must close work and spans before awaiting `client.stop()`. |
| `setupNodePropagation(trustedUrls?)` | string/regular-expression destination allowlist | `void` | Idempotently installs W3C trace/baggage propagation and Node HTTP instrumentation, including synchronized CommonJS and ESM `node:http`/`node:https` exports. Preload `@anectico/sdk/node/register` before application imports. An application propagator registered first is preserved and should include both W3C Trace Context and W3C Baggage. |
| `setupNodeFrameworkInstrumentation()` | — | `void` | Idempotently registers privacy-safe GraphQL execution/resolver and Prisma operation/query instrumentation. The Node preload calls this before application imports. |
| `anecticoExpressMiddleware(client, options?)` | client; `ExpressMiddlewareOptions` | Express handler | Creates request spans and extracts trace context. Identity baggage is ignored unless explicitly trusted. |
| `anecticoExpressErrorHandler(client)` | client | Express error handler | Captures route errors and must be registered after routes. |
| `anecticoFastifyPlugin(instance, options)` | Fastify instance; `AnecticoFastifyPluginOptions` | `Promise<void>` | Registers global lifecycle hooks for request spans, async request identity, errors, timeouts, body aborts, and response disconnects. |
| `@anectico/sdk/browser` `initBrowser(options?)` | `AnecticoOptions` | `Promise<AnecticoClient>` | Starts browser transport, global handlers, and low-overhead fetch/XHR tracing with W3C propagation. It does not clone response bodies or allocate a Resource Timing observer per request. |
| `registerBrowserHandlers(client)` | client | cleanup function | Installs global error/rejection and lifecycle flush handlers. Stops on a non-persisted `pagehide`; canceled navigation and back/forward-cache suspension leave the client running. |
| `AnecticoProvider` | `client`, children | React node | Exposes the client through React context. |
| `useAnectico()` | — | `AnecticoClient` | Reads the provider client; throws outside `AnecticoProvider`. |
| `useSpan(name)` | span name | `{ span, endSpan }` | Creates a component-lifetime span and ends it on unmount. |
| `AnecticoErrorBoundary` | client, children, optional fallback/hooks | React component | Captures render errors while preserving custom fallback behavior. |

`ExpressMiddlewareOptions` supports `ignorePaths`, safe `recordHeaders`, `trustIncomingIdentity`,
`requestIdentityHook`, and `spanNameHook`. The identity hook resolves a server-authenticated
customer for the current request, overrides untrusted inbound identity baggage, propagates the
identity downstream, and rebinds the matching preload HTTP root. Exact and trailing-`*` ignored
paths also suppress the generic incoming HTTP span installed by the Node preload. Sensitive
headers are redacted even when requested.

Browser fetch/XHR, Express, Fastify, and Node HTTP spans strip URL userinfo,
query strings, and fragments. Each HTTP attribute is bounded independently and
marked with a `.truncated=true` sibling when shortened. Node telemetry uses only
request scope or trusted incoming baggage for person attribution; it never assigns
unscoped background work the process-global anonymous identity.

For a managed raw Node HTTP server without the Express or Fastify integration,
set `ignoreIncomingRequestPaths` on `initNode()` (or the comma-separated
`ANECTICO_IGNORE_INCOMING_REQUEST_PATHS`) to suppress health, readiness, and
internal control traffic. Matching supports exact paths and a trailing `*`;
query strings do not affect matching.

`AnecticoFastifyPluginOptions` requires `client` and supports the same ignore/header/trust/span-name
controls plus `requestIdentityHook`. The identity hook resolves the authenticated customer for the
current request without mutating process-global SDK identity, and rebinds the matching preload HTTP
root so every span in the server trace has the same customer. Its ignored paths likewise apply to
both Fastify and preload HTTP spans.

For NestJS Apollo and Prisma, preload `@anectico/sdk/node/register` before the
application entry point. Named GraphQL operations and bounded resolver spans are
recorded automatically; literal values are replaced with `*`, variables are not
recorded, repeated list paths are merged, and trivial property resolvers are
omitted. GraphQL execution errors promote the named operation span to error status,
record the original resolver exception, and add `graphql.error.count` even when
Apollo returns HTTP `200`. Prisma operations and parameterized database queries
appear as child spans, making repeated N+1 work visible.

For Next.js App Router, preload `@anectico/sdk/node/register` with `NODE_OPTIONS` before
`next start`, then memoize one `initNode()` client from the root
`instrumentation.ts` `register` hook. An async `onRequestError` hook may await
`captureError`, but it must not attach request headers, cookies, query values, or
action payloads. Add only trusted same-application or downstream origins to
`propagateTraceHeaderUrls`; internal Route Handler calls are not trusted
implicitly. Next's OpenTelemetry spans then preserve browser → Server Action →
`fetch` → Route Handler causality and identity. Name business Actions and attach
bounded safe attributes through the active OpenTelemetry span; never record
`FormData`, payment tokens, or other server-action secrets.

## LLM wrappers and source maps

| API | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `wrapOpenAI(client, anectico, options?)` | OpenAI client; Anectico client; `{ captureContent? }` | same OpenAI client | Instruments `chat.completions.create` calls in place, streaming and non-streaming. Idempotent. |
| `wrapAnthropic(client, anectico, options?)` | Anthropic client; Anectico client; `{ captureContent? }` | same Anthropic client | Instruments `messages.create` calls in place, streaming and non-streaming. Idempotent. |
| `uploadSourceMap(options)` | release, dist, filename, content, endpoint, API key, optional fetch and `timeoutMs` | `Promise<void>` | Uploads one source map with a 30-second default deadline. CI should normally use `anectico symbols upload-sourcemap`. |
| `collectSourceMaps(directory, fs)` | directory; required filesystem facade | `CollectedSourceMap[]` | Finds `.map` files for a custom uploader. The packaged CLI supplies the Node filesystem adapter. |

A streamed call from either wrapper is recorded as one span when the stream is exhausted, fails, or
is abandoned; the stream object and its chunks or events are returned unchanged, and a stream that is
never iterated is never recorded.

Stream telemetry retains the identity and trace context present when the provider call began,
even if another request later consumes the stream. Non-streaming calls preserve provider promise
helpers such as `withResponse()`. Source-map uploads reject redirects, close unused response
bodies, and exclude symlinks and special files when collecting a directory.

Abandoning a stream — breaking out of the loop, or closing it early — is not an error. The span keeps
its normal status and carries `anectico.gen_ai.stream.abandoned = true` instead, so early exits stay
out of your error rate while remaining distinguishable. Any token counts on such a span are the
provider's last reported values and are partial by construction. A mid-stream failure is an error, and
the provider's error reaches you unchanged.

Neither wrapper changes your request. OpenAI token counts come only from the provider's final usage
chunk, so request `stream_options: { include_usage: true }` yourself — the wrapper never adds it,
because doing so inserts an extra empty-`choices` chunk into your loop and is rejected outright by
some OpenAI-compatible gateways. Anthropic needs no such parameter. A count the provider never
reported is recorded as absent, never as zero. `captureContent` defaults to `false` and can record
sensitive prompts and completions.

## Validation and utility exports

These root exports support framework authors and custom integrations. Normal applications should
prefer `init`, `AnecticoClient`, and the documented subpath clients.

| API | Parameters | Returns/behavior |
| --- | --- | --- |
| `resolveConfig(options?)` | partial `AnecticoOptions` | Resolves environment values, defaults, and explicit options into `ResolvedConfig`. It does not validate required values. |
| `validateConfig(config)` | resolved config | `void`; throws for missing credentials/service identity, invalid URL/protocol/rates, or inconsistent batching/timeouts. |
| `validateAPIKey(key)` | string | `void`; throws when the key does not match the supported API-key shape. |
| `isAPIKeyFormat(value)` | string | Boolean shape check without throwing. |
| `maskAPIKey(key)` | string | Log-safe prefix plus last four characters. Never use the result for authentication. |
| `generateErrorId()` | — | Hyphenless random error/message ID. |
| `parseStackTrace(error, maxFrames=50)` | `Error`; limit | `StackFrame[]` parsed from V8, Firefox, or Safari stacks. |
| `formatStackTrace(frames)` | `StackFrame[]` | OpenTelemetry stacktrace string. |
| `framesToSerializable(frames)` | camel-case frames | Backend wire frames with `in_app`. |
| `resolveCaptureOptions(options?, defaultLevel='error')` | capture options/default level | Capture defaults used by custom capture implementations. |
| `new UserManager()` | — | Mutable global error-user holder with `setUser`, `getUser`, and `clearUser`; values are copied. |
| `userToAttributes(user)` | `AnecticoUser` | Flat OpenTelemetry `enduser.*` and `user.data.*` string attributes. |
| `new IdentityManager(storage?)` | optional Web Storage-like adapter | Identity holder with `getDistinctId`, `getAnonId`, `identify`, `group`, `getGroups`, and `reset`. |
| `sharedIdentity()` | — | Process/page-wide `IdentityManager` shared by core signals and analytics. |
| `contextWithDistinctId(ctx, id)` | OTel context and ID | New context carrying request-local identity and W3C baggage. |
| `distinctIdFromContext(ctx)` | OTel context | Request-local ID or `undefined`. |
| `adoptDistinctIdFromBaggage(ctx)` | trusted OTel context | New context that promotes baggage identity; authenticate first. |
| `new BreadcrumbBuffer(capacity=100)` | positive capacity | Ring buffer with `size`, `isEmpty`, `add`, `serialize`, and `clear`. |
| `createTransport(config)` | validated `ResolvedConfig` | `TransportComponents` containing enabled OTel providers and signal handles. |
| `flushTransport(components, timeoutMs)` | components/deadline | `Promise<void>`; force-flushes all providers without stopping them and reports aggregated failures. |
| `shutdownTransport(components, timeoutMs)` | components/deadline | `Promise<void>`; shuts down all providers and reports aggregated failures. |

The Node subpath additionally exports `removeDistinctIdFromBaggage(ctx)` for stripping an inbound
Anectico identity member while retaining trace context, plus the request-context helpers listed above.

- [Install and use JavaScript](/docs/instrument/javascript)
- [Identify customers](/docs/instrument/identity)
- [Record session replay](/docs/instrument/session-replay)
