JavaScript and TypeScript
Instrument Node.js, browser, Express, Fastify, NestJS GraphQL, Prisma, and React applications with the Anectico SDK.
On this page
Use @anectico/sdk for Node.js, browser, Express, Fastify, NestJS GraphQL, Prisma, and React applications. The base package sends
traces, metrics, logs, errors, diagnostic events, and customer identity; replay and LLM wrappers
are opt-in subpath imports.
Install and start
During early access, install the artifact supplied during onboarding. Replace the path with the actual downloaded package:
npm install /path/to/anectico-sdk.tgz
After the public registry release, use npm install @anectico/sdk instead.
import { initNode } from '@anectico/sdk/node';
const anectico = await initNode({
apiKey: process.env.ANECTICO_API_KEY, // native_ingest
captureApiKey: process.env.ANECTICO_CAPTURE_API_KEY, // server_capture
serviceName: 'orders-api',
serviceVersion: process.env.GIT_SHA,
environment: process.env.NODE_ENV,
});
Create two keys for the same project using native intake keys.
apiKey / ANECTICO_API_KEY takes the native_ingest key for OTLP.
captureApiKey / ANECTICO_CAPTURE_API_KEY takes the server_capture key for the core client's
identify() and group() calls. The keys must differ. A telemetry-only client can omit
captureApiKey; a non-empty identify and group call then throws before changing identity or sending
anything. Explicitly denied product consent remains a no-op. Key purpose is verified by the server;
the SDK can validate format and separation, but cannot infer purpose from an opaque secret.
This bootstrap is for Node.js and installs asynchronous trace context propagation. For a browser,
use the browser setup below; the base init() does not install Node's context manager.
Keep one client for the process. Call await anectico.stop() during graceful shutdown so buffered
telemetry can finish exporting.
For a raw Node HTTP server in managed mode, exclude health and internal control traffic from the generic incoming HTTP spans:
const anectico = await initNode({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'orders-api',
ignoreIncomingRequestPaths: ['/healthz', '/ready', '/internal/*'],
});
The equivalent ANECTICO_IGNORE_INCOMING_REQUEST_PATHS environment variable is a
comma-separated list. Exact paths and trailing-* prefixes are supported, and
query strings are ignored for matching.
initNode() provides default SIGTERM and SIGINT flushing only while Anectico is the sole listener
for that signal. When your application registers its own handler—for example to drain a queue or
close WebSocket sessions—it owns the ordering. Stop accepting work, close the connections and end
their spans, then await anectico.stop(). Anectico detects application listeners whether they were
registered before or after initNode() and will not race them by stopping the exporter first.
Concurrent stop() calls share the same in-flight shutdown. The first uncaught exception or
unhandled rejection owns one fatal capture, flush, and exit sequence, so a secondary terminal event
cannot return early and cut off the original flush.
For Kubernetes, set terminationGracePeriodSeconds to cover application draining plus Anectico's two
managed shutdown phases. Identity and group delivery is bounded by shutdownTimeout, then the trace,
metric, and log providers shut down concurrently under another shutdownTimeout; the defaults
therefore need at least 10 seconds after application work closes.
SIGKILL, kill -9, forced zero-grace pod deletion, and host loss cannot run JavaScript handlers.
They create no terminal capture and perform no final flush. The lost tail may include active spans,
in-flight identity and group requests, up to maxQueueSize ended spans and logs per batched signal
(2,048 by default), and metrics accumulated since the last metricExportInterval. Use SIGTERM
and a non-zero grace period for deploys and ordinary shutdowns.
Serverless invocation lifecycle
Initialize one client promise at module scope so a warm AWS Lambda-style runtime reuses the same
providers. At the end of every invocation, call await anectico.flush() rather than stop(): flush()
waits for identity requests and forces buffered traces, logs, and metrics out before the runtime may
freeze, while keeping the client running for the next invocation.
import { context } from '@opentelemetry/api';
import { contextWithDistinctId, initNode } from '@anectico/sdk/node';
const anecticoPromise = initNode({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'invoice-automation',
});
export async function handler(event: { customerId: string }) {
const anectico = await anecticoPromise;
const invocationContext = contextWithDistinctId(context.active(), event.customerId);
try {
return await context.with(invocationContext, async () => {
return await anectico.tracer.startActiveSpan('lambda.invoke', async span => {
try {
return await automateInvoice(event);
} finally {
span.end();
}
});
});
} finally {
await anectico.flush();
}
}
Do not call initNode() inside the handler or call stop() after a successful invocation. Repeated
in-handler initialization creates competing providers, while stop() permanently closes the
client. Reserve stop() for controlled process teardown. Keep person identity invocation-scoped
with contextWithDistinctId; do not switch the process-global identity for concurrent requests.
Capture a connected operation
With the Node bootstrap above, make the operation's span active so errors and child spans share its trace. The identity below is for a single-user script; in a concurrent server, supply identity through the framework's request context instead.
The first argument is the identifier. Use a stable id from your own system; it is stored
exactly as sent. An email address sent as a property is stored as [REDACTED], so nobody can
search by it or read it later. See
What Anectico redacts and withholds.
anectico.identify('user_8842', { email: 'buyer@acme.example', plan: 'pro' });
await anectico.tracer.startActiveSpan('process-order', async span => {
try {
await processOrder();
} catch (error) {
const failure = error as Error;
const errorId = anectico.captureError(failure, { tags: { component: 'checkout' } });
anectico.logEvent('error', failure.message, { 'error.id': errorId });
throw error;
} finally {
span.end();
}
});
Pass the ID returned by captureError() to logEvent() on the same client to keep the log
searchable and trace-correlated without creating a second Issue. The client remembers its most
recent maxQueueSize sampled captures (2,048 by default), including across flush() calls.
Stopping the client or withdrawing diagnostics consent clears those links. Logs with unrelated
IDs, or whose capture was disabled or sampled out, remain independent Issue sources.
Call anectico.reset() on logout. In a browser it first stops active replay recorders and flushes their
prior-user tail before rotating identity and session. It also clears uncaptured breadcrumbs, groups,
and global error-user context; other already-captured telemetry keeps its original attribution.
Start a new recorder after identifying the next user if replay should resume. In a multi-user
server, prefer framework request context over changing the process-wide identity for every request.
Record an application metric
anectico.recordMetric('checkout.duration_ms', durationMs, {
route: '/checkout',
result: 'success',
});
recordMetric records one histogram observation and reuses the instrument by name. Keep labels
low-cardinality; do not add customer, order, request, trace, or session IDs. Use an existing
OpenTelemetry metrics pipeline when you need an explicit counter or gauge.
Express, Fastify, and service propagation
import express, { type Request } from 'express';
import { AnecticoClient } from '@anectico/sdk';
import { anecticoExpressErrorHandler, anecticoExpressMiddleware } from '@anectico/sdk/node';
const app = express();
const anectico = await new AnecticoClient({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'orders-api',
}).start();
// Install your authentication middleware before this middleware. It must verify
// credentials before setting request.user; never copy an untrusted identity header.
app.use(anecticoExpressMiddleware(anectico, {
requestIdentityHook: request =>
(request as Request & { user?: { id: string } }).user?.id,
}));
// Register routes here.
app.use(anecticoExpressErrorHandler(anectico)); // Always last.
Resolve Express identity from server-authenticated request state with
requestIdentityHook; the SDK scopes it to that request, propagates it to trusted
downstream services, and applies it to the preload HTTP span. Do not call the
process-global identify() method for each concurrent server request.
When captureError() records a sampled exception inside an Express request, that
exception owns the failure's Issue occurrence. The Express and incoming HTTP request
spans stay visible with their original error status and link to the captured error,
including when you call await anectico.flush() before sending the response. Requests
without a recorded, sampled capture retain their failed-request Issue fallback.
A capture on an independent trace or in another request on the same trace does not
suppress the request's Issue.
For Fastify, register the Anectico plugin before application hooks and routes. Resolve
identity from authenticated request state; do not call the process-global
identify() method for each concurrent server request.
import Fastify, { type FastifyRequest } from 'fastify';
import { initNode, anecticoFastifyPlugin } from '@anectico/sdk/node';
const app = Fastify();
const anectico = await initNode({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'payments-api',
propagateTraceHeaderUrls: ['https://authorization.internal.example'],
});
await app.register(anecticoFastifyPlugin, {
client: anectico,
ignorePaths: ['/health', '/internal/*'],
requestIdentityHook: request =>
(request as FastifyRequest & { user?: { id: string } }).user?.id,
});
// Add your authentication hook before registering routes. Only assign request.user
// after verifying credentials. Anonymous requests may leave it undefined.
The Fastify plugin keeps trace and identity context active across async lifecycle
hooks and route handlers. It ends each request span once on a response, timeout,
request-body abort, or response-socket disconnect. Exact and trailing-*
ignorePaths suppress both the plugin span and the generic incoming HTTP span
installed by the Node preload. When requestIdentityHook resolves an authenticated
customer, that identity is applied to both spans so the complete server trace has
one customer even though the preload span starts before Fastify authentication.
The middleware extracts W3C trace context. Identity baggage is ignored by default because a public
client can forge it. Change trustIncomingIdentity to true only behind a gateway that strips and
recreates client-supplied baggage.
For outbound Node http/https propagation, preload the SDK before application modules and allow
only trusted destinations:
node --import @anectico/sdk/node/register ./app.mjs
import { initNode } from '@anectico/sdk/node';
const anectico = await initNode({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'orders-api',
propagateTraceHeaderUrls: ['https://billing.internal.example'],
});
Trusted Node fetch calls are traced and receive traceparent plus baggage automatically.
The preload is still required when http/https modules may load before Anectico; the fetch
wrapper itself is installed by initNode, either framework integration, or the preload.
The preload, initNode, Express middleware, and Fastify plugin share one idempotent setup path, so
using them together does not register OpenTelemetry globals more than once. If your application installs a global
propagator before Anectico, Anectico preserves it; include both W3C Trace Context and W3C Baggage in that
propagator when you want anectico.distinct_id to cross trusted service boundaries.
If the application already owns a complete OpenTelemetry SDK, do not add the preload or let Anectico
register a second set of providers and instrumentations. Start the application SDK first and pass
openTelemetryMode: 'existing' to initNode(). Anectico then uses the registered global tracer, meter,
and logger while the application retains flush and shutdown ownership. See
OpenTelemetry migration.
NestJS, Apollo GraphQL, and Prisma
Use the same preload before the NestJS entry point:
node --require @anectico/sdk/node/register dist/main.js
For an ESM build, use node --import @anectico/sdk/node/register dist/main.mjs.
The preload registers GraphQL and Prisma instrumentation before those packages
load. Anectico records named operation spans, bounded resolver spans, and Prisma
operation and query children without customer-authored spans.
Privacy defaults are fixed: GraphQL literal values become *, variables are not
attached to spans, repeated list paths are merged, and trivial property resolvers
are omitted. When Apollo returns GraphQL errors with HTTP 200, Anectico marks the
named operation span as failed, records the original resolver exception, and adds
graphql.error.count. Parameterize Prisma queries and avoid including secrets in
exception messages.
If a fatal Node exception or unhandled rejection occurs during startup, Anectico prints the original error and stack to stderr before its best-effort flush. The SDK does not hide the diagnostic by installing its process handlers.
Next.js App Router and Server Actions
Use Next's root instrumentation.ts hook to start one server client, and preload
Anectico before the Next production server so generic inbound HTTP instrumentation is
installed before Next imports Node's HTTP modules:
NODE_OPTIONS='--require @anectico/sdk/node/register' next start
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
const { getAnectico } = await import('./src/observability');
await getAnectico();
}
export async function onRequestError(error: unknown) {
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
const { getAnectico } = await import('./src/observability');
const anectico = await getAnectico();
anectico.captureError(
error instanceof Error ? error : new Error('Next request failed'),
{ tags: { 'error.mechanism': 'next.onRequestError' } },
);
}
getAnectico() should memoize one initNode() promise for the process. Configure
propagateTraceHeaderUrls with only the trusted origins that Server Actions call,
including the application's own origin when an Action uses an internal Route
Handler. This lets the browser request, Server Action, outbound fetch, and Route
Handler retain one trace and anectico.distinct_id:
const anectico = await initNode({
apiKey: process.env.ANECTICO_API_KEY,
serviceName: 'subscription-commerce',
propagateTraceHeaderUrls: ['https://shop.example.com'],
});
Next emits App Router render, Action, Route Handler, and fetch spans through the
global OpenTelemetry provider. For a business Action, give the active span a safe
operation name and bounded correlation attributes with @opentelemetry/api.
Never attach FormData, action payloads, cookies, authorization headers, payment
tokens, or unbounded business objects. Use the same safe idempotency identifier on
failure and retry, and capture the exception once so one failure remains visible
without multiplying Issue occurrences.
Initialize @anectico/sdk/browser from a Client Component after hydration. It starts with
diagnostics, performance, product and replay collection denied. Apply your application's
explicit grants through client.collection.setConsent(...), and share that controller with
analytics and replay. See the browser consent and Web Vitals example.
Browser keys are intentionally public: scope each to the project and give it a single purpose
(native_ingest, browser_capture, replay:write, or a client delivery key with only
flags:evaluate or surveys:respond). Keep management and read scopes, flags:read included, on the
server. initApp() wires all of them in one call.
Explicit product page views
Create an analytics client after any consent your application requires, using a project key
with purpose browser_capture and your ingestion endpoint:
import { AnalyticsClient } from '@anectico/sdk/analytics';
const events = new AnalyticsClient({
endpoint: 'https://api.anectico.com',
apiKey: import.meta.env.VITE_ANECTICO_CAPTURE_API_KEY,
release: import.meta.env.VITE_ANECTICO_RELEASE,
appVersion: import.meta.env.VITE_ANECTICO_RELEASE,
});
events.page('Pricing', { plan: 'free' });
Call page() explicitly when the page or client-side route is ready. It records $pageview
with $page_name and the current identity, session and groups. It does not install navigation
tracking or collect the browser URL, title or referrer. Use stable page names and approved
properties; the SDK does not automatically redact arbitrary supplied properties.
await events.disable() withdraws analytics collection and discards queued events. Use
await events.flush() when checking queue acknowledgement; check stats() for drops or
uncertain delivery. See the JavaScript SDK reference for
retry and in-memory queue limits.
Reserved $release / $app_version properties
Every event capture()s (including page()) carries the reserved $release and
$app_version string properties when configured, never invented when they are not. Set them
explicitly with the release/appVersion constructor options above. When you omit them, Node
processes fall back to the ANECTICO_RELEASE/ANECTICO_APP_VERSION environment variables, and
browser builds fall back to <meta name="anectico-release" content="…"> /
<meta name="anectico-app-version" content="…"> tags — the same build-time-injection pattern
already used for source-map release. An app that sets none of these sends events with neither
property. See Reserved event properties.
Send revenue
To measure money with revenue analytics, send each payment with
captureRevenue. It is capture() with the amount in $revenue and the currency in $currency,
and it uses the same consent, queue and retry behavior:
events.captureRevenue('purchase', { amount: 19.99, currency: 'USD', properties: { plan: 'pro' } });
events.captureRevenue('refund', { amount: -5, currency: 'USD', properties: { plan: 'pro' } });
amount is a number in major units (19.99, not 1999) and is negative for a refund; it may
have at most four decimal places and a size of at most 99,999,999,999.9999. currency is three
upper-case letters. The call throws, instead of sending a value revenue analytics would leave out,
when either is invalid; amounts are never rounded or converted. $revenue and $currency win over
same-named keys in properties. See the reference.
Capture interactions automatically
Instead of instrumenting every button, opt in to autocapture. It records clicks, field changes
and form submits as $autocapture, repeated clicks as $rageclick, and, through page tracking,
$pageleave with time on page and scroll depth. Nothing starts until you call it, and it follows
the same product consent as the rest of analytics.
import { AnalyticsClient } from '@anectico/sdk/analytics';
import { startAutocapture } from '@anectico/sdk/autocapture';
const routes = ['/', '/pricing', '/orders/:id'];
const events = new AnalyticsClient({ endpoint, apiKey: browserCaptureKey, collection });
events.trackPageViews({ routes });
const autocapture = startAutocapture(events, {
routes,
blockSelectors: ['.payment-form'], // no events at all from here
maskSelectors: ['[data-account]'], // events, but no text or attributes
});
collection.setConsent({ product: true });
Events carry the page's route template, never its address, and never the content of an input,
textarea, select or editable field. Mark anything else sensitive with data-anectico-block or
data-anectico-mask, or the equivalent rr-block / rr-mask and
anectico-block / anectico-mask classes. These markers also apply to replay, click friction
and page snapshots. Pass the same local selectors to every collector you start; set
maskAllText: true to send no visible autocapture text at all. Call
autocapture.stop() to remove it. The
JavaScript SDK reference
lists every option, event property, rate limit and privacy rule.
Control autocapture without a redeploy
Add remoteConfig: true and autocapture obeys the project's
capture settings, which an
administrator or an agent can change at any time:
const autocapture = startAutocapture(events, { routes, remoteConfig: true });
- Capture stays off until the settings have been read. If they cannot be read, it stays off.
- A project's settings start with autocapture off. Turn it on once for the project
(
anectico capture-settings enable-autocapture). - The settings only narrow what your code configured: they can turn capture off, mask more, block more and lower limits. They cannot add events, unmask text or raise a limit.
- Turning autocapture off for the project stops it in open pages at their next refresh (five minutes by default) and in newly loaded pages within about 30 seconds.
See server-controlled settings for the exact rule per option and the counters that show what was applied.
Ask in-product questions
To show a short survey to your visitors, ask your agent to create and launch it (create_survey and
launch_survey over MCP, or anectico surveys create and anectico surveys launch), then add
@anectico/sdk/surveys to your page. The SDK shows only the surveys the server says the current
visitor may see on the current page, one at a time, and records the display, the answers and any
dismissal as events on the person. Nothing starts until you call it, and it follows the same
product consent as the rest of analytics.
Surveys use their own key. Create a client delivery key that holds only surveys:respond
(anectico apikey create --purpose client_delivery --scope surveys:respond) and use it here; it is embedded in your page, so it must carry nothing else. Use the app host, the one flag
decisions use, as the endpoint:
import { AnalyticsClient } from '@anectico/sdk/analytics';
import { startSurveys } from '@anectico/sdk/surveys';
const routes = ['/', '/billing', '/orders/:id'];
const events = new AnalyticsClient({ endpoint, apiKey: browserCaptureKey, collection });
events.trackPageViews({ routes });
const surveys = startSurveys(events, {
endpoint: 'https://app.anectico.com',
apiKey: surveysKey, // surveys:respond only
routes, // the same templates page tracking uses
environment: 'production',
});
collection.setConsent({ product: true });
The default panel is accessible, sits in the bottom corner without blocking the page and takes its
colours, radius and font from a few CSS custom properties. It sends the page as one of your
declared route templates, never the address. To draw your own UI, pass render: false and an
onSurvey callback, then report what happens with surveys.shown(survey),
surveys.submit(survey, answers) and surveys.dismiss(survey). Call surveys.stop() to remove it.
If a survey is no longer active, or the visitor already answered, the SDK stops showing it and does
not retry. A network failure while sending answers keeps them and retries with the same response
id, so one answer is recorded once. The
JavaScript SDK reference lists every option, the widget styling
properties, the error codes and the counters in stats().
Browser applications: one entry point
For the problem-report widget, reuse the same project-scoped
browser_capture key as capture; it can submit reports but cannot list, read or delete them.
initApp() from @anectico/sdk/app sets up telemetry, product events, replay and feature flags in
one call, sharing one identity, one session and one consent controller.
import { initApp } from '@anectico/sdk/app';
const anectico = await initApp({
endpoint: 'https://api.anectico.com',
appEndpoint: 'https://app.anectico.com', // needed for replay and flags
serviceName: 'storefront',
release: import.meta.env.VITE_APP_RELEASE,
keys: {
ingest: import.meta.env.VITE_ANECTICO_API_KEY, // native_ingest
capture: import.meta.env.VITE_ANECTICO_CAPTURE_API_KEY, // browser_capture
replay: import.meta.env.VITE_ANECTICO_REPLAY_API_KEY, // replay:write, optional
},
// Required. Every category starts denied; state what your application may collect.
consent: { diagnostics: true, performance: true, product: true, replay: true },
});
anectico.identify('customer-123', { plan: 'pro' });
anectico.page('Pricing');
anectico.capture('checkout_started', { plan: 'pro' });
consentis required. Anectico collects nothing for a category you leave out; change it later withanectico.setConsent({ replay: false }). Pass your own controller instead of grants if the application already has one.- Replay is loaded only when you supply
keys.replay, so applications that do not record do not download the recorder. Replay uploads go toappEndpoint, the same host as the Console and the REST API. Do not passkeys.flagsin a browser build: that flag client calls the decide route, which needsflags:readand reads every flag definition. Evaluate flags from the page as shown in Feature flags in the browser. anectico.reset()on logout rotates the identity and session and starts a fresh recording.anectico.client,anectico.events,anectico.flagsandanectico.replayexpose the underlying clients described in the rest of this page.
The keys stay separate on purpose. Each is a public, project-scoped credential that can do exactly one thing, so a leaked replay key cannot write events.
React and browser credentials
import { AnecticoClient } from '@anectico/sdk';
import { AnecticoErrorBoundary, AnecticoProvider } from '@anectico/sdk/react';
const anectico = await new AnecticoClient({
apiKey: import.meta.env.VITE_ANECTICO_API_KEY, // native_ingest
captureApiKey: import.meta.env.VITE_ANECTICO_CAPTURE_API_KEY, // browser_capture
serviceName: 'web',
release: import.meta.env.VITE_APP_RELEASE,
}).start();
root.render(
<AnecticoProvider client={anectico}>
<AnecticoErrorBoundary client={anectico} fallback={<ErrorPage />}>
<App />
</AnecticoErrorBoundary>
</AnecticoProvider>,
);
Use separate project-scoped native_ingest and browser_capture keys in browser builds.
Standalone AnalyticsClient.apiKey takes the browser capture key. Replay takes a separate key
with only replay:write; client-side feature flags take a client delivery key with only
flags:evaluate, which returns decisions for the keys you name and never a definition. Never expose
flags:read or any other read, management, or MCP scope.
Feature flags in the browser
Create a client delivery key with only flags:evaluate (anectico apikey create --purpose client_delivery --scope flags:evaluate) and ask for the keys the page needs:
// A client delivery key that holds only flags:evaluate. It is public: anyone who opens the app can read it.
const response = await fetch('https://app.anectico.com/api/v1/flags/evaluate', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': import.meta.env.VITE_ANECTICO_CLIENT_FLAGS_KEY },
body: JSON.stringify({
distinct_id: 'user_8842',
flag_keys: ['new-checkout'],
person_properties: { plan: 'pro' },
}),
});
const { decisions, evaluated_at, max_age_seconds } = await response.json();
const decision = decisions['new-checkout'];
// Only an evaluated true turns the feature on; unavailable and errors keep the safe default.
const showNewCheckout = decision.state === 'evaluated' && decision.bool_value === true;
Each requested key comes back with a state: evaluated, evaluation_error (the value, when present,
is a marked fallback) or unavailable_flag (no value at all; it is not false). Use the answer until
evaluated_at plus max_age_seconds (30 seconds), then ask again. Record the exposure through your
capture client as $feature_flag_called with $feature_flag_version set to the decision's
definition_version; see the flag verification reference.
The answer uses only the properties you send: stored profile properties and audiences that the key
may not read are not used. The full contract is in Feature flags from a page or
app. Restrict cross-origin propagation with
propagateTraceHeaderCorsUrls.
Logs, replay, and AI calls
Use the client's log and error methods for manual signals. Import @anectico/sdk/replay only on pages
where browser replay is needed; inputs are masked by default. The @anectico/sdk/openai and
@anectico/sdk/anthropic wrappers both record streaming and non-streaming model calls — a streamed
call is recorded as one span when the stream completes, fails, or is abandoned — with content capture
off by default. For an OpenAI stream, pass stream_options: { include_usage: true } so token counts
and cost are available.
Verify and recover
Generate one request for a known test customer, then ask your agent to read that customer's timeline
(get_person_timeline over MCP, or anectico persons timeline <customer-id>) and confirm the error,
trace, and logs share that customer and trace. The MCP result carries a link that opens the customer as
a read-only proof page. If nothing arrives, enable ANECTICO_DEBUG=true,
confirm the key and endpoint, and stop the process gracefully before checking again.
- Identify customers
- Runnable integration examples: a Node server with product events and feature flags, checked end to end
- Feature flags in your application and Plan and prove product events: have your agent wire a Node server and prove the exact samples arrived
- Record application metrics
- Record session replay
- Record LLM calls
- JavaScript SDK API reference
- Configuration reference