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:
- Retry only transient transport failures,
429,502,503, and504. - Reuse the logical event's
event_id. - Apply exponential backoff with jitter.
- Cap attempts and elapsed time.
- 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: calltelemetry.initbefore 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.429or5xx: 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.