TypeScript SDK

Beta

ESM, zero runtime dependencies, uses the global fetch.

Install

Node 18+ or any runtime with a global fetch. ESM, zero runtime dependencies, uses the global fetch.

npm
npm install @counters.dev/sdk

Initialize

Create a client with your organization API key. The base URL defaults to the production host; override it with baseUrl for local dev.

client.ts
import { CountersClient } from "@counters.dev/sdk";

const client = new CountersClient({
  apiKey: process.env.COUNTERS_API_KEY!,
});

Count

add() and subtract() are buffered — the SDK coalesces writes per counter and flushes them as a single POST /batch. Use addNow()/subtractNow() for an immediate write that returns the new counter state. amount accepts a number, bigint, or decimal string.

count
const registrations = client.counter("registrations");

registrations.add(1);   // buffered
registrations.add(5);   // coalesced with the line above
await client.flush();   // one POST /batch

// Immediate write — returns the new counter:
const c = await registrations.addNow(1);
console.log(c.value);   // "42" — a string (arbitrary precision)

Read

Read the current value, or a time series of per-bucket deltas. from/to accept an ISO string or a Date; bucket is one of 1m, 5m, 1h, 1d, 1w, 1mo (finer buckets are plan-gated).

read
const { value } = await registrations.value();

const series = await registrations.series({
  from: "2026-01-01T00:00:00Z",
  to: "2026-01-08T00:00:00Z",
  bucket: "1d",
});

Manage

clear() resets the counter to zero (a new epoch — history is retained); delete() tombstones it; list() pages the organization's counters.

manage
await registrations.clear();
await registrations.delete();

const page = await client.list({ limit: 50 });

Lifecycle

Buffered writes flush on an interval and at maxBatchSize. Always close() (or flush()) before your process exits so no buffered write is lost.

lifecycle
await client.close(); // flush + stop the background timer

Errors

Invalid keys or amounts throw CountersValidationError before any request is sent. Failed requests throw CountersError, carrying .status and the RFC 9457 .problem body.

errors
import { CountersError, CountersValidationError } from "@counters.dev/sdk";

try {
  await registrations.addNow(1);
} catch (e) {
  if (e instanceof CountersValidationError) throw e; // bad input
  if (e instanceof CountersError) console.error(e.status, e.problem);
}
Prefer raw HTTP? See the REST API reference. Create a key in your dashboard.