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/nodeServer-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
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Project API key for test or live. |
baseUrl | string | https://api.usekite.cloud/v1 | HTTP(S) ingestion API base URL. |
timeout | number | 5000 | Per-attempt timeout in milliseconds. |
retries | number | 3 | Retries after the initial attempt. Use 0 to disable. |
onError | (error: KiteError) => void | none | Observability 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
| Field | Type | Required | Description |
|---|---|---|---|
customerId | string | yes | Stable ID from your own system. |
event | string | yes | Event name declared in kite.config.ts. |
properties | Record<string, unknown> | no | Data matching the event schema. |
timestamp | Date | no | When the event occurred; defaults to ingestion time. |
idempotencyKey | string | no | Stable 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';| Error | Meaning |
|---|---|
KiteError | Base API, serialization, or response error. Includes optional statusCode and code. |
KiteValidationError | Invalid client input or an HTTP 400/422 response. Includes optional field. |
KiteNetworkError | Network 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
429responses; - HTTP
5xxresponses.
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:
import { Kite } from '@kitesdk/node';
export const kite = new Kite({
apiKey: process.env.KITE_API_KEY!,
onError: (error) => console.error(error),
});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.