npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tallyify/sdk

v1.0.0

Published

Privacy-first usage and cost telemetry SDK for Tallyify.

Readme

Tallyify SDK

npm version node license

Privacy-first usage & cost telemetry for your AI calls.

@tallyify/sdk wraps your AI provider calls (OpenAI, Anthropic, Google Gemini, DeepSeek, Mistral, Groq, Azure OpenAI, AWS Bedrock) and sends only aggregated usage and cost metrics to Tallyify — never your prompts, responses, or provider API keys.

Your telemetry shows up in Profile → API Usage on Tallyify (the same data the @tallyify/cli reads with tally usage).

  • 🔒 Privacy by design — sends token counts, estimated cost, latency, status, and sanitized metadata. Nothing else.
  • 🪶 Zero runtime dependencies — uses the native fetch in Node 18+.
  • 🛟 Best-effort & non-blocking — telemetry never throws and never breaks your provider call. If Tallyify is unreachable, it's silently skipped.
  • 💸 Built-in cost estimation — live pricing from the Tallyify catalog, with a bundled fallback price table.
  • 🚦 Budget guardrails — flag or throw when spend crosses a threshold.

Table of contents


Install

npm install @tallyify/sdk

Requires Node 18+ (for global fetch). TypeScript types are bundled.

import { Tracker, trackOpenAI } from '@tallyify/sdk';
const { Tracker, trackOpenAI } = require('@tallyify/sdk');

You'll need a Tallyify API key (tly_live_… / tly_test_…) — create one in your Tallyify profile → API Keys.


Quick start

Adapter (recommended)

Wrap your client once; every call is tracked automatically.

import OpenAI from 'openai';
import { trackOpenAI } from '@tallyify/sdk';

const openai = trackOpenAI(new OpenAI(), {
  trackerApiKey: process.env.TALLYIFY_API_KEY!,
  service: 'billing-api',
});

const res = await openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Hello!' }],
});
// Usage + cost telemetry dispatched to Tallyify in the background.

Explicit wrapper

Full control, one call at a time.

import OpenAI from 'openai';
import { Tracker } from '@tallyify/sdk';

const openai = new OpenAI();
const tracker = new Tracker({ apiKey: process.env.TALLYIFY_API_KEY! });

const res = await tracker.track(
  openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: 'Hello!' }],
  }),
  { provider: 'openai', model: 'gpt-4o-mini', endpoint: 'chat.completions' }
);

Two ways to track

| Mode | When to use it | How | | --- | --- | --- | | Provider adapter | "Set & forget" — wrap the client once, use it normally | trackOpenAI(client, config), trackAnthropic(...), … | | Explicit wrapper | Per-call control, or providers without a dedicated adapter | tracker.track(promise, options) |

Both go through the same pipeline: extract usage → estimate cost → enforce budget → dispatch telemetry. The adapters return a copy of your client with the relevant methods wrapped — your original client is never mutated.


Tracker configuration

new Tracker(config: TrackerConfig)

| Field | Type | Default | Description | | --- | --- | --- | --- | | apiKey | string | required | Your Tallyify key. Should start with tly_live_ / tly_test_. Throws if missing. | | baseUrl | string | https://api.tallyify.com | API base. Trailing slashes are stripped. Local dev: http://localhost:3000. | | service | string | — | Logical name of the calling service. Added to metadata.service. | | debug | boolean | false | Log telemetry/pricing failures to console.warn. | | privacy | object | — | capturePrompts/captureResponses are disabled by design (setting them just warns). redactSecrets is always on. | | dynamicPricing | boolean | true | Load the live pricing catalog from /v1/feed to price every model. | | budget | BudgetConfig | — | Spend guardrails (see below). |

The same options can be passed to every adapter via ProviderAdapterConfig (trackerApiKey instead of apiKey, plus an optional metadata object applied to every event from that client).


Tracker API

track<T>(providerPromise, options?): Promise<T>

Wraps a provider call promise and returns the provider's response unchanged after dispatching telemetry.

  • On provider error: dispatches a status: 'error' event with latency, then re-throws the original error.
  • If the response is an async iterable (a stream): returns a wrapped stream that accumulates usage as you consume chunks (see Streaming).

TrackOptions

| Field | Type | Description | | --- | --- | --- | | provider | string | openai, anthropic, google, … Inferred from the model name if omitted. | | model | string | Model name. Read from the response if omitted. | | endpoint | string | Endpoint label (chat.completions, messages, …). | | metadata | Record<string, string\|number\|boolean> | Custom metadata (max 20 keys, sanitized). | | retryCount | number | Retries already performed (recorded as retry_count). |

trackWithRetries<T>(factory, options?): Promise<T>

Like track, but takes a factory (() => Promise<T>) so each attempt is a fresh call. Records retry_count and emits one telemetry event per attempt.

RetryOptions (extends TrackOptions)

| Field | Type | Default | Description | | --- | --- | --- | --- | | retries | number | 3 | Max attempts (including the first). | | retryDelayMs | number | 250 | Base delay with exponential backoff (base * 2^attempt). | | shouldRetry | (error, attempt) => boolean | () => true | Decide whether an error is retryable. |

const res = await tracker.trackWithRetries(
  () => openai.chat.completions.create({ model: 'gpt-4o', messages }),
  { provider: 'openai', model: 'gpt-4o', retries: 4, shouldRetry: (e) => isTransient(e) }
);

getTotalCost(): number

Cumulative estimated cost (USD) tracked by this instance so far.


Streaming

When the provider returns a stream, track detects the async iterable and returns a wrapper that:

  1. yields every chunk unchanged (consume it with for await);
  2. accumulates usage by taking the max of each field — this handles both the OpenAI style (full usage in the final chunk) and the Anthropic style (cumulative usage split across message_start / message_delta);
  3. dispatches a single streamed: true event when the stream ends.
const stream = await tracker.track(
  openai.chat.completions.create({
    model: 'gpt-4o',
    messages,
    stream: true,
    stream_options: { include_usage: true },
  }),
  { provider: 'openai', model: 'gpt-4o' }
);

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}
// Telemetry is dispatched automatically when the loop finishes.

For OpenAI, pass stream_options: { include_usage: true } — otherwise usage isn't present in the chunks and token counts will be 0.


Cost estimation & dynamic pricing

Cost is estimated as:

cost = (input_tokens  / 1e6) * input_price_per_1M
     + (output_tokens / 1e6) * output_price_per_1M

Pricing sources, in priority order:

  1. Live catalog (dynamicPricing: true, default) — the SDK calls GET /v1/feed?limit=2000 once and builds a provider:model → {input, output} map, normalizing units (1M / 1k / each) to price-per-1M-tokens.
  2. Bundled fallback table — covers common OpenAI / Anthropic / Google / DeepSeek / Groq / Mistral models.

Model lookup is forgiving: it tries provider:model, then a normalized key that strips Bedrock vendor prefixes and date/version/-latest suffixes (e.g. anthropic.claude-3-5-sonnet-20240620-v1:0claude-3-5-sonnet). Azure OpenAI aliases to the openai price table.

If no price is found, cost_estimate stays undefined — the platform can recompute it server-side from its authoritative catalog.


Budgets & spend guardrails

Costs are only known after a call returns, so the budget is a post-hoc tripwire: it surfaces overspend, it does not prevent it.

BudgetConfig

| Field | Type | Description | | --- | --- | --- | | maxCostPerCall | number | Threshold (USD) for a single call's estimated cost. | | maxTotalCost | number | Threshold (USD) for this Tracker's cumulative cost. | | enforce | boolean | If true, throw TallyifyBudgetError instead of only warning. | | onExceeded | (info: BudgetExceededInfo) => void | Called whenever a threshold is crossed. |

const tracker = new Tracker({
  apiKey: process.env.TALLYIFY_API_KEY!,
  budget: {
    maxTotalCost: 5.0,
    enforce: true,
    onExceeded: (i) => alertOps(`Budget ${i.kind}: $${i.cost} > $${i.limit}`),
  },
});

TallyifyBudgetError exposes .info (kind, limit, cost, totalCost, provider, model). It's thrown after a successful provider call, so it's never confused with a provider error.


Privacy & secret redaction

  • The SDK sends only aggregated metrics: provider, model, token counts, estimated cost, endpoint, latency, status, retry count, streamed flag, metadata.
  • Metadata is sanitized: max 20 keys, keys truncated to 64 chars, string values to 256 chars. Any key/value that looks like a secret (sk-…, Bearer …, *_API_KEY=, long opaque blobs) is replaced with [REDACTED].
  • service is added to metadata automatically.
  • Prompt/response capture is not a feature — it's intentionally disabled.

Never pass a provider API key to the SDK. The SDK only ever transmits the aggregated usage fields listed above; your provider keys are used solely by the provider's own SDK and never reach Tallyify.


Provider adapters

Every adapter has the signature track<Provider>(client, config: ProviderAdapterConfig): client.

| Function | Wrapped methods | provider | | --- | --- | --- | | trackOpenAI | chat.completions.create, responses.create, embeddings.create | openai | | trackAnthropic | messages.create, beta.messages.create | anthropic | | trackGemini | generateContent, models.generateContent | google | | trackDeepSeek | chat.completions.create | deepseek | | trackMistral | chat.complete, chat.completions.create, embeddings.create | mistral | | trackGroq | chat.completions.create (OpenAI-compatible) | groq | | trackAzureOpenAI | chat.completions.create, responses.create, embeddings.create | azure-openai (prices via openai alias) | | trackBedrock | send(command) — model read from command.input.modelId | bedrock |

import Anthropic from '@anthropic-ai/sdk';
import { trackAnthropic } from '@tallyify/sdk';

const anthropic = trackAnthropic(new Anthropic(), {
  trackerApiKey: process.env.TALLYIFY_API_KEY!,
  service: 'support-bot',
});

await anthropic.messages.create({
  model: 'claude-3-5-sonnet-latest',
  max_tokens: 256,
  messages: [{ role: 'user', content: 'Hi' }],
});

Provider normalization (applied client- and server-side): gemini / google-geminigoogle, azure / azure_openaiazure-openai, aws / amazon / bedrock-runtimebedrock.


The payload sent to Tallyify

POST {baseUrl}/v1/track, header Authorization: Bearer <tly_…>, JSON body:

{
  "provider": "openai",
  "model": "gpt-4o-mini",
  "input_tokens": 1200,
  "output_tokens": 350,
  "total_tokens": 1550,
  "cached_tokens": 0,
  "reasoning_tokens": 0,
  "cost_estimate": 0.00039,
  "endpoint": "chat.completions",
  "latency_ms": 812,
  "status": "success",
  "retry_count": 0,
  "streamed": true,
  "metadata": { "service": "billing-api", "feature": "summarize" }
}

The server validates and normalizes the payload, recomputes cost from its own catalog where possible, rate-limits per key, and stores it in your usage history.


Authentication & scopes

Authenticate with a Tallyify API key. Required scopes:

| Capability | Scope | Plan | | --- | --- | --- | | Send telemetry (/v1/track) | track:write | any | | Dynamic pricing feed (/v1/feed) | pricing:read | Enterprise |

Without pricing:read, dynamic pricing degrades silently to the bundled fallback table — telemetry still works.


Environment variables

The SDK doesn't read the environment for you; you pass values in. By convention:

| Variable | Use | | --- | --- | | TALLYIFY_API_KEY | the key passed to apiKey / trackerApiKey | | TALLYIFY_BASE_URL | base URL (e.g. http://localhost:3000 in dev) |


Errors

| Error | When | | --- | --- | | Error('Tallyify SDK requires a TALLYIFY_API_KEY value.') | apiKey missing in the constructor. | | TallyifyBudgetError | A budget threshold is exceeded with enforce: true. Exposes .info. | | console warnings | Wrong key prefix, un-inferable provider, telemetry/pricing failures (only with debug: true). |

Network failures to Tallyify do not throw — telemetry is best-effort.


TypeScript

Fully typed. Notable exports:

import {
  Tracker,
  TallyifyBudgetError,
  trackOpenAI, trackAnthropic, trackGemini, trackDeepSeek,
  trackMistral, trackGroq, trackAzureOpenAI, trackBedrock,
  type TrackerConfig, type TrackOptions, type RetryOptions,
  type BudgetConfig, type TrackEventPayload, type ProviderAdapterConfig,
} from '@tallyify/sdk';

License

ISC. See LICENSE.

Built for Tallyify — track, compare, and analyze AI model pricing and usage.