Apitally JavaScript SDK v1: rebuilt on OpenTelemetry

The new SDK brings built-in sampling for logs and traces, plus custom attributes for requests and API consumers.

Simon GurckeSimon Gurcke//4 min read

Today we’re releasing v1 of the Apitally JavaScript SDK. Rebuilt from scratch on OpenTelemetry, it introduces built-in sampling and custom attributes for requests and API consumers.

Apitally is a simple API observability tool for small engineering teams. It helps you understand API usage, monitor errors and performance, and troubleshoot individual requests with correlated logs and traces. The new SDK gives you more control over what you capture, while keeping setup to a few lines of code.

Rebuilt on OpenTelemetry

The SDK is now an OpenTelemetry distribution. It automatically configures OpenTelemetry and sends metrics, logs, and traces to Apitally using OTLP, the industry-standard telemetry data protocol.

You don’t need to understand OpenTelemetry to use Apitally, but if you already have it configured, the SDK works alongside your existing instrumentation and exporters. Just add ApitallySpanProcessor to your tracer provider.

You can use standard instrumentation packages and APIs to trace database queries and external HTTP calls. For your own code, the SDK now provides instrument() and span() helpers to trace individual functions and blocks of code. See the tracing guide for more details and examples.

Other noteworthy changes

Setup changes

A new shared useApitally() setup function works across many supported frameworks:

import { Hono } from "hono";
import { useApitally } from "apitally";

const app = new Hono();
useApitally(app, { writeToken: "your-write-token", env: "dev" });

The SDK now authenticates using write tokens, which you can create and revoke in the Apitally dashboard. Instead of passing a write token in code, you can set it via the environment variable APITALLY_WRITE_TOKEN. The environment, such as dev or prod, can also be set via APITALLY_ENV.

You no longer need to explicitly enable request logging, tracing, or application log capture, as these are now enabled by default. You can still opt out, of course.

Sampling for request logs and traces

Sampling lets you capture detailed logs and traces for only a fraction of your traffic while keeping metrics for every request. This gives you control over data capture volume and quota usage as your API grows.

You can set a fixed sampleRate or use the sampleOnRequest / sampleOnResponse callbacks for custom sampling logic.

For example, this callback captures 100% of error responses (4xx, 5xx) and only 10% of successful responses:

import { useApitally } from "apitally";

function sampleOnResponse(span) {
  const statusCode = span.attributes["http.response.status_code"];
  if (typeof statusCode === "number" && statusCode >= 400) {
    return 1.0; // keep all
  }
  return 0.1; // keep only 10%
}

useApitally(app, {
  sampleOnResponse,
});

The sampling callbacks can use request/response details, consumer identity, or custom attributes set by your application. See the sampling guide for more details and examples.

More control over application logs

The new maskLogRecord callback lets you redact sensitive values or drop individual application log records before export.

For example, this callback excludes debug-level application logs:

import { useApitally } from "apitally";

function maskLogRecord(record) {
  if (record.severityText === "debug") {
    return null; // drop record
  }
  return record;
}

useApitally(app, {
  maskLogRecord,
});

More context for requests and consumers

Custom request attributes let you attach application-specific details to a request’s trace. Add them with setRequestAttribute() anywhere during request handling.

import { setRequestAttribute } from "apitally";

setRequestAttribute("tenant.id", "acme");
setRequestAttribute("order.id", "ord_123");

API consumers can now also carry custom attributes alongside their identifier, name, and group. Call setConsumer() during request handling to identify the consumer and pass their custom attributes as an attributes object. The same helper now works across all supported frameworks:

import { setConsumer } from "apitally";

setConsumer({
  identifier: "[email protected]",
  name: "John Doe",
  group: "Users",
  attributes: { tenant: "abc" },
});

Tested and benchmarked

We’ve put a lot of effort into testing the new SDK, so you can rely on it in production.

Daily end-to-end tests run against real applications across framework and runtime versions, checking that the SDK captures the expected metrics, logs, and traces.

We also benchmark the same applications with and without the SDK to measure its impact on latency, CPU usage, and memory. Longer load tests keep those applications busy at high throughput to check how performance and resource usage hold up over time.

We’ll share more about how we test and benchmark the SDKs in a dedicated post, which will also include detailed benchmark results.

What’s next

The new SDK lays the groundwork for broader application monitoring, including background jobs, workers, or scheduled tasks. The aim is to make monitoring these parts of an application as straightforward as API monitoring is with Apitally today.

The OpenTelemetry foundation will also enable support for custom metrics. For example, if you charge for API usage in credits, you could track each consumer’s credit usage in Apitally.

Getting started

Our setup guides walk you through installing and configuring the SDK for your framework.

If you’re already using Apitally, follow the migration guide to upgrade to v1.

If you have feedback or questions about the SDK, please let us know.