Docs

SDK Reference

Send customer traits and product events from a server-side Node.js application.

@kitesdk/node is the server-side ingestion client for Node.js 20 or newer. Use it to identify customers and record meaningful product behavior.

npm install @kitesdk/node

Server-side only

Kite API keys are project credentials. Never expose a kt_test_... or kt_live_... key in browser code, mobile applications, public logs, or source control.

Create a client

Create one client and reuse it throughout your backend process:

import { Kite } from '@kitesdk/node';

export const kite = new Kite({
  apiKey: process.env.KITE_API_KEY!,
  onError(error) {
    console.error('Kite delivery failed', {
      name: error.name,
      message: error.message,
      code: error.code,
      statusCode: error.statusCode,
    });
  },
});

The API key determines the environment. Keys beginning with kt_test_ send to test; keys beginning with kt_live_ send to live.

Client options

OptionTypeDefaultDescription
apiKeystringrequiredProject API key for test or live.
baseUrlstringhttps://api.usekite.cloud/v1HTTP(S) ingestion API base URL.
timeoutnumber5000Per-attempt timeout in milliseconds.
retriesnumber3Retries after the initial attempt. Use 0 to disable.
onError(error: KiteError) => voidnoneObservability callback for failed delivery.

Invalid client options throw KiteValidationError immediately during construction. The client exposes the detected environment as kite.environment.

Track events

track records something that happened for a stable customer ID.

const result = await kite.track({
  customerId: 'customer_123',
  event: 'report.created',
  properties: {
    reportId: 'report_456',
    format: 'pdf',
  },
  idempotencyKey: 'report_456_created',
});

Track options

FieldTypeRequiredDescription
customerIdstringyesStable ID from your own system.
eventstringyesEvent name declared in kite.config.ts.
propertiesRecord<string, unknown>noData matching the event schema.
timestampDatenoWhen the event occurred; defaults to ingestion time.
idempotencyKeystringnoStable key used to deduplicate retries.

Use timestamp when importing or delivering an event after it occurred:

await kite.track({
  customerId: 'customer_123',
  event: 'subscription.renewed',
  timestamp: new Date('2026-07-20T14:30:00Z'),
});

On success, track returns:

interface TrackResult {
  eventId: string;
  accepted: boolean;
  queued: boolean;
  duplicate: boolean;
}

duplicate: true means Kite already accepted the same idempotency key.

Identify customers

identify writes customer traits used by lifecycle, segment, rule, and health conditions.

const result = await kite.identify({
  customerId: 'customer_123',
  traits: {
    plan: 'growth',
    seats: 12,
    renewal_date: '2026-12-01T00:00:00Z',
  },
});

traits is an alias with the same behavior and signature:

await kite.traits({
  customerId: 'customer_123',
  traits: { seats: 18 },
});

Both methods use the batch endpoint and return BatchResult | undefined.

Batch operations

Use batch to send ordered identify and track operations in one request:

const result = await kite.batch([
  {
    type: 'identify',
    customerId: 'customer_123',
    traits: { plan: 'growth' },
  },
  {
    type: 'track',
    customerId: 'customer_123',
    event: 'workspace.invited',
    properties: { invitedRole: 'admin' },
    idempotencyKey: 'invite_789',
  },
]);

Operations are sent in array order. The result is:

interface BatchResult {
  accepted: number;
  rejected: number;
  eventIds: string[];
}

Delivery and errors

Delivery methods intentionally use a no-throw contract:

const result = await kite.track({
  customerId: 'customer_123',
  event: 'feature.used',
});

if (!result) {
  // Delivery failed and onError has already been called.
}

track, identify, traits, and batch resolve to undefined after a failed request and report the failure to onError. Even if onError throws, the delivery method still resolves to undefined.

Always configure onError

Without onError, failed delivery still resolves to undefined but produces no application log or metric. Connect the callback to your existing observability system.

Error types

import {
  KiteError,
  KiteNetworkError,
  KiteValidationError,
} from '@kitesdk/node';
ErrorMeaning
KiteErrorBase API, serialization, or response error. Includes optional statusCode and code.
KiteValidationErrorInvalid client input or an HTTP 400/422 response. Includes optional field.
KiteNetworkErrorNetwork failure or timeout after retries. Has retryable: true.

Constructor validation is the exception to the no-throw delivery contract:

try {
  const kite = new Kite({ apiKey: process.env.KITE_API_KEY! });
} catch (error) {
  // Invalid API key or client option.
}

Retries and idempotency

The SDK retries:

  • network failures and timeouts;
  • HTTP 429 responses;
  • HTTP 5xx responses.

It honors Retry-After for rate limits and otherwise uses exponential backoff beginning at 100 ms. Validation and other non-retryable 4xx responses are not retried.

When retries is greater than 0, track operations without an explicit idempotency key get a generated UUID. The same generated key and serialized payload are reused for every attempt. For business operations with a natural unique ID, supply your own stable key so duplicate calls across processes are also deduplicated.

await kite.track({
  customerId: 'customer_123',
  event: 'invoice.paid',
  properties: { invoiceId: 'invoice_456' },
  idempotencyKey: 'invoice_456_paid',
});

Local development

Point the SDK at the API URL and temporary key printed by kite dev:

const kite = new Kite({
  apiKey: process.env.KITE_API_KEY!,
  baseUrl: 'http://127.0.0.1:4401/v1',
  retries: 0,
});

Local engine state is in memory and resets when kite dev stops. Use a test key when sending to Kite Cloud.

Framework lifecycle

Create the client at module scope rather than inside each request handler:

lib/kite.ts
import { Kite } from '@kitesdk/node';

export const kite = new Kite({
  apiKey: process.env.KITE_API_KEY!,
  onError: (error) => console.error(error),
});
app/api/reports/route.ts
import { kite } from '@/lib/kite';

export async function POST() {
  // Create the report first.
  await kite.track({
    customerId: 'customer_123',
    event: 'report.created',
  });

  return Response.json({ ok: true });
}

Choose intentionally whether product-event delivery is part of the request path or handled by your own durable job queue. The SDK retries transient HTTP failures, but it is not a persistent queue and does not survive process termination.

Type exports

The package exports the client, errors, and public request/result types:

import type {
  Environment,
  KiteOptions,
  TrackOptions,
  IdentifyOptions,
  TraitsOptions,
  BatchOperation,
  TrackResult,
  BatchResult,
} from '@kitesdk/node';

Define event names and property schemas in the Config Reference, then use stable customer identity conventions from Identity and events.

On this page