Telemetry
Browse docs

Use this doc with your coding agent

Open a focused prompt pack for Claude Code, Codex, Cursor, or another coding agent, then adapt it to the workflow covered here.

On this page
  1. Install and initialize
  2. Send one structured event
  3. Send a batch
  4. Run a typed query
  5. Delivery behavior and retries
  6. Verify the integration
  7. Troubleshooting

JavaScript and TypeScript SDK

Use the telemetry-sh package in server-side JavaScript or TypeScript. The current client exposes immediate log and query calls through both ESM and CommonJS builds. It does not maintain a background event queue or expose a flush method.

Do not initialize the package in browser code: a Telemetry API key grants access to a team and must not be shipped in a client bundle.

Install and initialize

npm install telemetry-sh

ES modules:

import telemetry from "telemetry-sh";

telemetry.init(process.env.TELEMETRY_API_KEY);

CommonJS:

const telemetry = require("telemetry-sh");

telemetry.init(process.env.TELEMETRY_API_KEY);

Call init once during server or worker startup. Use a write-scoped key for ingestion-only code and a read-scoped key for reporting or query-only automation.

Send one structured event

Await the returned promise when the application needs to observe delivery success or failure:

const eventId = crypto.randomUUID();

try {
  await telemetry.log("api_request_completed", {
    event_id: eventId,
    route_template: "/api/projects/:id",
    method: "POST",
    status_code: 201,
    status: "success",
    latency_ms: 184,
    environment: process.env.APP_ENV,
    release: process.env.APP_RELEASE,
  });
} catch (error) {
  console.error("Telemetry delivery failed", {
    event_id: eventId,
    error_type: "telemetry_delivery_failed",
  });
}

Keep raw URLs, request bodies, cookies, authorization headers, secrets, prompts, and private customer content out of the payload. Use stable route templates, internal identifiers, and controlled error categories.

Send a batch

log accepts an array of compatible objects. A batch reduces request overhead but increases the number of events affected by one failed request.

await telemetry.log("job_completed", [
  {
    event_id: "evt_job_101",
    job_name: "invoice_sync",
    status: "success",
    duration_ms: 912,
  },
  {
    event_id: "evt_job_102",
    job_name: "invoice_sync",
    status: "failed",
    duration_ms: 2401,
    error_type: "provider_timeout",
  },
]);

The JavaScript client sends the supplied array immediately. It does not collect calls into an internal batch. If the application introduces its own buffer, bound its size, age, retry budget, and shutdown behavior as described in batching and backpressure.

Run a typed query

type ReliabilityRow = {
  requests: number;
  route_template: string;
};

const result = await telemetry.query<ReliabilityRow>(`
  SELECT
    route_template,
    COUNT(*) AS requests
  FROM api_request_completed
  WHERE timestamp_utc >= now() - INTERVAL '24 hours'
  GROUP BY route_template
  ORDER BY requests DESC
`);

for (const row of result.data) {
  console.log(row.route_template, row.requests);
}

The generic type describes result rows for TypeScript; it does not validate SQL results at runtime. Check empty results and unexpected nulls before using a query in automation.

The SDK's query method calls the interactive query endpoint. Use the documented HTTP flow for asynchronous JSON or Parquet exports rather than assuming that an SDK option creates and polls an async job.

Delivery behavior and retries

The current package performs one fetch request for each log or query call. It does not add an SDK timeout, automatic retry, persistent queue, or flush lifecycle.

If retrying ingestion:

  1. Retry only transient transport failures, 429, 502, 503, and 504.
  2. Reuse the logical event's event_id.
  3. Apply exponential backoff with jitter.
  4. Cap attempts and elapsed time.
  5. Do not turn a completed customer action into a failure unless telemetry is explicitly part of that workflow's durability contract.

Use an application-owned durable outbox for billing or approved audit events that cannot be dropped. See event delivery and idempotency.

Verify the integration

After sending synthetic success and failure events, run:

SELECT
  timestamp_utc,
  event_id,
  route_template,
  status,
  latency_ms,
  error_type
FROM api_request_completed
ORDER BY timestamp_utc DESC
LIMIT 20;

Confirm table name, field types, null behavior, UTC timestamps, and the absence of sensitive fields. Then test retry, timeout, and shutdown branches before creating a dashboard or alert.

Troubleshooting

  • API key is not initialized: call telemetry.init before the first SDK method.
  • 401: replace the missing, invalid, or revoked key.
  • 403: use a key with the required scope.
  • 400: inspect the table name, JSON shape, and field-type compatibility; do not retry unchanged.
  • 429 or 5xx: use a bounded retry policy if the event can safely be delivered more than once.
  • Process exits before delivery: track and await immediate calls, or persist required events before shutdown; there is no SDK flush queue.

Continue with the Log API, rate limits and API errors, and the Node.js and Express integration.

Related product capability

Capture stable event names, typed fields, and privacy-reviewed context.

Ownership and technical references

The Telemetry editorial team owns this explanation; the product team reviews behavior, examples, and boundaries.

Review the editorial standard