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

ai-chargeback

v0.5.3

Published

Tag and allocate AI API costs by team, project, or feature

Readme

ai-chargeback

Tag and allocate AI API costs by team, project, or feature.

npm version npm downloads license node TypeScript

ai-chargeback is a cost allocation library for AI API usage. It attaches cost center metadata -- team, project, feature, environment -- to every AI API call, accumulates token usage and dollar costs per tag combination, and produces chargeback data that breaks down AI spend by any dimension. It answers the question every enterprise AI platform team eventually faces: "Which team, project, or feature is responsible for which portion of our AI API bill?"

Installation

npm install ai-chargeback

Quick Start

import { createTracker } from 'ai-chargeback';

const tracker = createTracker();

// Record an AI API call with cost center tags
const record = await tracker.record({
  tags: { team: 'search', project: 'autocomplete', feature: 'suggestions' },
  model: 'gpt-4o',
  inputTokens: 1500,
  outputTokens: 400,
});

console.log(record.cost);      // 0.00775 (auto-computed from built-in pricing)
console.log(record.provider);  // 'openai' (auto-inferred from model name)

// Query stored records
const records = await tracker.query({ tags: { team: 'search' } });
console.log(`Search team calls: ${records.length}`);

// Clean up
await tracker.close();

Features

  • Tag-based cost allocation -- Attach arbitrary key-value tags (team, project, feature, environment, cost center) to every AI API call for multi-dimensional cost attribution.
  • Automatic cost computation -- Built-in pricing table for 16 models across OpenAI, Anthropic, and Google. Costs are computed automatically from token counts when not provided explicitly.
  • Provider inference -- Automatically detects the provider (openai, anthropic, google) from the model name. Supports explicit override.
  • Configurable tag governance -- Enforce allowed tag keys, required tag keys, and default tags at the tracker level. Tag keys are validated against format rules, reserved prefixes, and count limits.
  • Buffered writes -- Records are buffered in memory and flushed to storage in batches, configurable by record count and time interval.
  • Pluggable storage -- Ships with an in-memory adapter. Supports custom adapters via the StorageAdapter interface for databases, file systems, or cloud storage.
  • Flexible querying -- Filter stored records by date range, tags, models, and providers.
  • Custom pricing -- Override built-in pricing or add pricing for custom/private models.
  • Zero runtime dependencies -- Only uses Node.js built-ins.

API Reference

createTracker(config?)

Creates a CostTracker instance. All configuration is optional; defaults to in-memory storage with built-in pricing.

function createTracker(config?: Partial<ChargebackConfig>): CostTracker;

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | config | Partial<ChargebackConfig> | Optional tracker configuration |

Returns: CostTracker

import { createTracker } from 'ai-chargeback';

// Minimal -- in-memory storage, built-in pricing
const tracker = createTracker();

// Fully configured
const tracker = createTracker({
  storage: { type: 'memory' },
  pricing: {
    'my-private-model': { input: 5.00, output: 20.00 },
  },
  buffer: { maxRecords: 50, maxIntervalMs: 3000 },
  defaultTags: { environment: 'production' },
  allowedTagKeys: ['team', 'project', 'feature', 'environment'],
  requiredTagKeys: ['team'],
});

CostTracker

The interface returned by createTracker. All methods are asynchronous.

tracker.record(input)

Records an AI API call. Validates tags, computes cost if not provided, infers provider from model name, and buffers the record for storage.

record(input: RecordInput): Promise<CostRecord>

Parameters:

| Field | Type | Required | Description | |-------|------|----------|-------------| | input.tags | Tags | Yes | Key-value pairs for cost attribution | | input.model | string | Yes | Model identifier (e.g., 'gpt-4o', 'claude-sonnet-4-20250514') | | input.inputTokens | number | Yes | Number of input/prompt tokens | | input.outputTokens | number | Yes | Number of output/completion tokens | | input.provider | string | No | Provider name. Auto-inferred if omitted. | | input.cost | number | No | Explicit cost in USD. Auto-computed from pricing if omitted. | | input.metadata | Record<string, unknown> | No | Arbitrary metadata attached to the record |

Returns: CostRecord -- the created record with id, timestamp, computed cost, inferred provider, and totalTokens.

Throws: ChargebackValidationError if tags fail validation. ChargebackConfigError if the tracker has been closed.

const record = await tracker.record({
  tags: { team: 'ml', project: 'summarizer' },
  model: 'claude-sonnet-4-20250514',
  inputTokens: 2000,
  outputTokens: 800,
  metadata: { requestId: 'req-abc-123' },
});
// record.id         -> UUID v4
// record.timestamp  -> ISO 8601
// record.provider   -> 'anthropic'
// record.cost       -> 0.018
// record.totalTokens -> 2800

tracker.query(filters?)

Retrieves stored records matching the given filters. Flushes the buffer before querying.

query(filters?: QueryFilters): Promise<CostRecord[]>

Parameters:

| Field | Type | Description | |-------|------|-------------| | filters.from | string | ISO 8601 start date (inclusive) | | filters.to | string | ISO 8601 end date (inclusive) | | filters.tags | Tags | Filter by tag key-value pairs (AND logic) | | filters.models | string[] | Filter by model names (OR logic) | | filters.providers | string[] | Filter by provider names (OR logic) |

Returns: CostRecord[]

// All records for the search team using OpenAI models
const records = await tracker.query({
  tags: { team: 'search' },
  providers: ['openai'],
});

// Records within a date range
const marchRecords = await tracker.query({
  from: '2026-03-01T00:00:00.000Z',
  to: '2026-03-31T23:59:59.000Z',
});

tracker.count(filters?)

Returns the number of records matching the given filters.

count(filters?: QueryFilters): Promise<number>
const total = await tracker.count();
const mlCount = await tracker.count({ tags: { team: 'ml' } });

tracker.flush()

Immediately writes all buffered records to storage.

flush(): Promise<void>
await tracker.record({ tags: { team: 'test' }, model: 'gpt-4o', inputTokens: 100, outputTokens: 50 });
await tracker.flush(); // Records are now persisted to storage

tracker.purge(filters)

Removes records matching the given filters from storage. Flushes the buffer first.

purge(filters: QueryFilters): Promise<number>

Returns: The number of records removed.

// Remove all records for the staging environment
const removed = await tracker.purge({ tags: { environment: 'staging' } });
console.log(`Purged ${removed} records`);

// Remove records older than a date
const purged = await tracker.purge({ to: '2026-01-01T00:00:00.000Z' });

tracker.close()

Flushes remaining buffered records, stops the flush interval timer, and closes the storage adapter. After calling close(), any subsequent call to record() throws a ChargebackConfigError. Calling close() multiple times is safe (idempotent).

close(): Promise<void>
await tracker.close();
// tracker.record(...) will now throw ChargebackConfigError: "Tracker is closed"

Pricing Functions

getPrice(model, customPricing?)

Looks up the per-token pricing for a model. Checks custom pricing first, then built-in pricing. Supports date-suffixed model names (e.g., 'gpt-4o-2024-08-06' resolves to 'gpt-4o').

function getPrice(
  model: string,
  customPricing?: Record<string, ModelPricing>,
): ModelPricing | undefined;

Returns: ModelPricing if found, undefined otherwise.

import { getPrice } from 'ai-chargeback';

getPrice('gpt-4o');                // { input: 2.50, output: 10.00 }
getPrice('gpt-4o-2024-08-06');     // { input: 2.50, output: 10.00 } (date suffix stripped)
getPrice('unknown-model');          // undefined

// With custom pricing
getPrice('my-model', { 'my-model': { input: 5.00, output: 20.00 } });
// { input: 5.00, output: 20.00 }

computeCost(inputTokens, outputTokens, pricing)

Computes the dollar cost from token counts and per-million-token pricing.

function computeCost(
  inputTokens: number,
  outputTokens: number,
  pricing: ModelPricing,
): number;

Formula: (inputTokens / 1,000,000) * pricing.input + (outputTokens / 1,000,000) * pricing.output

import { computeCost } from 'ai-chargeback';

const cost = computeCost(1000, 500, { input: 2.50, output: 10.00 });
// (1000 / 1_000_000) * 2.50 + (500 / 1_000_000) * 10.00 = 0.0075

BUILT_IN_PRICING

A Record<string, ModelPricing> containing pricing for 16 models. Prices are in USD per million tokens.

| Model | Input ($/M tokens) | Output ($/M tokens) | |-------|-------------------:|--------------------:| | gpt-4o | 2.50 | 10.00 | | gpt-4o-mini | 0.15 | 0.60 | | gpt-4-turbo | 10.00 | 30.00 | | gpt-4 | 30.00 | 60.00 | | gpt-3.5-turbo | 0.50 | 1.50 | | o1 | 15.00 | 60.00 | | o1-mini | 3.00 | 12.00 | | o3-mini | 1.10 | 4.40 | | claude-opus-4-20250514 | 15.00 | 75.00 | | claude-sonnet-4-20250514 | 3.00 | 15.00 | | claude-haiku-3-20250307 | 0.80 | 4.00 | | claude-3-5-sonnet-20241022 | 3.00 | 15.00 | | claude-3-haiku-20240307 | 0.25 | 1.25 | | gemini-1.5-pro | 1.25 | 5.00 | | gemini-1.5-flash | 0.075 | 0.30 | | gemini-2.0-flash | 0.10 | 0.40 |


Validation Functions

validateTags(tags, options?)

Validates a tag set against all rules: key format, value constraints, count limits, allowed keys, and required keys.

function validateTags(
  tags: Tags,
  options?: {
    allowedTagKeys?: string[] | 'any';
    requiredTagKeys?: string[];
  },
): void;

Throws: ChargebackValidationError on any validation failure.

import { validateTags } from 'ai-chargeback';

// Passes
validateTags({ team: 'search', project: 'autocomplete' });

// Passes with governance
validateTags(
  { team: 'search', project: 'autocomplete' },
  { allowedTagKeys: ['team', 'project', 'feature'], requiredTagKeys: ['team'] },
);

// Throws: Tag key "123bad" is invalid
validateTags({ '123bad': 'value' });

Tag key rules:

  • Must start with a letter
  • May contain alphanumeric characters, underscores, dots, and hyphens
  • Must not use the reserved prefix _cb_
  • Maximum 20 tags per record

Tag value rules:

  • Must not be empty
  • Maximum 256 characters

validateTagKey(key)

Validates a single tag key against format rules and reserved prefix.

function validateTagKey(key: string): void;

Throws: ChargebackValidationError if the key is empty, uses the reserved _cb_ prefix, or contains invalid characters.


validateTagValue(key, value)

Validates a single tag value for length and emptiness.

function validateTagValue(key: string, value: string): void;

Throws: ChargebackValidationError if the value is empty or exceeds 256 characters.


MemoryStorageAdapter

An in-memory implementation of the StorageAdapter interface. Used as the default storage backend. Records are lost when the process exits.

class MemoryStorageAdapter implements StorageAdapter {
  append(records: CostRecord[]): Promise<void>;
  query(filters: QueryFilters): Promise<CostRecord[]>;
  purge(filters: QueryFilters): Promise<number>;
  close(): Promise<void>;
}
import { MemoryStorageAdapter } from 'ai-chargeback';

const adapter = new MemoryStorageAdapter();

Error Classes

ChargebackValidationError

Thrown when tag validation fails (invalid key format, empty value, exceeded limits, disallowed key, missing required key).

class ChargebackValidationError extends Error {
  readonly name: 'ChargebackValidationError';
}

ChargebackStorageError

Thrown when a storage operation fails. Includes an optional cause property for the underlying error.

class ChargebackStorageError extends Error {
  readonly name: 'ChargebackStorageError';
  readonly cause?: Error;
}

ChargebackConfigError

Thrown when the tracker is used incorrectly (e.g., recording after close()).

class ChargebackConfigError extends Error {
  readonly name: 'ChargebackConfigError';
}

Configuration

ChargebackConfig

The full configuration object for createTracker. All fields are optional.

interface ChargebackConfig {
  storage: StorageConfig;
  pricing?: Record<string, ModelPricing>;
  buffer?: { maxRecords: number; maxIntervalMs: number };
  defaultTags?: Tags;
  allowedTagKeys?: string[] | 'any';
  requiredTagKeys?: string[];
}

| Field | Type | Default | Description | |-------|------|---------|-------------| | storage | StorageConfig | { type: 'memory' } | Storage backend configuration | | pricing | Record<string, ModelPricing> | {} | Custom model pricing (USD per million tokens). Takes precedence over built-in pricing. | | buffer.maxRecords | number | 100 | Flush buffer to storage when it reaches this many records | | buffer.maxIntervalMs | number | 5000 | Flush buffer to storage at this interval (milliseconds). Set to 0 to disable interval-based flushing. | | defaultTags | Tags | {} | Tags merged into every record. Input tags override defaults for the same key. | | allowedTagKeys | string[] \| 'any' | 'any' | Restrict which tag keys can be used. Set to 'any' to allow all keys. | | requiredTagKeys | string[] | [] | Tag keys that must be present on every record |

StorageConfig

A discriminated union for selecting the storage backend.

type StorageConfig =
  | { type: 'memory' }
  | { type: 'file'; path: string }
  | { type: 'custom'; adapter: StorageAdapter };

| Type | Fields | Description | |------|--------|-------------| | 'memory' | -- | In-memory storage. Data is lost on process exit. | | 'file' | path: string | File-based JSON storage (path to the JSON file). | | 'custom' | adapter: StorageAdapter | Any object implementing the StorageAdapter interface. |

Error Handling

All errors thrown by ai-chargeback are instances of one of three specific error classes, all of which extend Error. This allows fine-grained error handling with instanceof checks.

import {
  createTracker,
  ChargebackValidationError,
  ChargebackStorageError,
  ChargebackConfigError,
} from 'ai-chargeback';

const tracker = createTracker({ requiredTagKeys: ['team'] });

try {
  await tracker.record({
    tags: { project: 'search' }, // missing required 'team' tag
    model: 'gpt-4o',
    inputTokens: 100,
    outputTokens: 50,
  });
} catch (err) {
  if (err instanceof ChargebackValidationError) {
    console.error('Tag validation failed:', err.message);
    // -> 'Required tag key "team" is missing'
  }
}

When each error is thrown:

| Error | Trigger | |-------|---------| | ChargebackValidationError | Invalid tag key format, empty tag value, value exceeding 256 chars, more than 20 tags, disallowed tag key, missing required tag key | | ChargebackStorageError | Storage adapter operation failure (includes cause property for the underlying error) | | ChargebackConfigError | Calling record() after close() |

Advanced Usage

Custom Storage Adapter

Implement the StorageAdapter interface to persist records to any backend.

import { createTracker } from 'ai-chargeback';
import type { StorageAdapter, CostRecord, QueryFilters } from 'ai-chargeback';

class PostgresStorageAdapter implements StorageAdapter {
  constructor(private pool: Pool) {}

  async append(records: CostRecord[]): Promise<void> {
    for (const r of records) {
      await this.pool.query(
        `INSERT INTO ai_costs (id, timestamp, tags, model, provider,
         input_tokens, output_tokens, total_tokens, cost, metadata)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
        [r.id, r.timestamp, JSON.stringify(r.tags), r.model, r.provider,
         r.inputTokens, r.outputTokens, r.totalTokens, r.cost,
         JSON.stringify(r.metadata)],
      );
    }
  }

  async query(filters: QueryFilters): Promise<CostRecord[]> {
    // Build WHERE clause from filters.from, filters.to, filters.tags, etc.
    // Return matching rows mapped to CostRecord objects
  }

  async purge(filters: QueryFilters): Promise<number> {
    // DELETE FROM ai_costs WHERE ...
    // Return the number of deleted rows
  }

  async close(): Promise<void> {
    await this.pool.end();
  }
}

const tracker = createTracker({
  storage: { type: 'custom', adapter: new PostgresStorageAdapter(pool) },
});

Tag Governance

Enforce a tag taxonomy across your organization by restricting allowed and required tag keys.

const tracker = createTracker({
  allowedTagKeys: ['team', 'project', 'feature', 'environment', 'costCenter'],
  requiredTagKeys: ['team'],
  defaultTags: { environment: 'production' },
});

// Succeeds -- 'team' is present, 'project' is allowed
await tracker.record({
  tags: { team: 'search', project: 'autocomplete' },
  model: 'gpt-4o',
  inputTokens: 500,
  outputTokens: 200,
});

// Throws ChargebackValidationError -- 'region' is not in allowedTagKeys
await tracker.record({
  tags: { team: 'search', region: 'us-east' },
  model: 'gpt-4o',
  inputTokens: 500,
  outputTokens: 200,
});

// Throws ChargebackValidationError -- required 'team' tag is missing
await tracker.record({
  tags: { project: 'autocomplete' },
  model: 'gpt-4o',
  inputTokens: 500,
  outputTokens: 200,
});

Custom Model Pricing

Add pricing for private or fine-tuned models, or override built-in pricing.

const tracker = createTracker({
  pricing: {
    'ft:gpt-4o:my-org:custom-model:abc123': { input: 3.75, output: 15.00 },
    'my-private-llama': { input: 0.50, output: 1.00 },
    'gpt-4o': { input: 2.00, output: 8.00 }, // override built-in pricing
  },
});

Buffer Tuning

Configure how frequently records are flushed from the in-memory buffer to the storage adapter.

// High-throughput: large buffer, infrequent flushes
const tracker = createTracker({
  buffer: { maxRecords: 500, maxIntervalMs: 10000 },
});

// Low-latency: small buffer, frequent flushes
const tracker = createTracker({
  buffer: { maxRecords: 10, maxIntervalMs: 1000 },
});

// Manual flush only: disable interval timer
const tracker = createTracker({
  buffer: { maxRecords: 1000, maxIntervalMs: 0 },
});

Provider Inference

The tracker automatically infers the provider from model name prefixes:

| Model prefix | Inferred provider | |-------------|-------------------| | gpt-*, o1*, o3*, o4* | openai | | claude-* | anthropic | | gemini-* | google | | All other | unknown |

Override by passing provider explicitly:

await tracker.record({
  tags: { team: 'platform' },
  model: 'gpt-4o',
  provider: 'azure', // Azure-hosted OpenAI model
  inputTokens: 1000,
  outputTokens: 500,
});

TypeScript

ai-chargeback is written in TypeScript with strict mode enabled. All types are exported from the package entry point.

import type {
  Tags,
  CostRecord,
  RecordInput,
  ModelPricing,
  StorageAdapter,
  StorageConfig,
  QueryFilters,
  ChargebackConfig,
  TaggedClientOptions,
  ExportFormat,
  ExportOptions,
  ReportOptions,
  CostTotals,
  CostBreakdown,
  TimeSeriesEntry,
  ChargebackReport,
  CostTracker,
} from 'ai-chargeback';

Key Types

// Arbitrary string key-value pairs for cost attribution
type Tags = Record<string, string>;

// What you pass to tracker.record()
interface RecordInput {
  tags: Tags;
  model: string;
  provider?: string;
  inputTokens: number;
  outputTokens: number;
  cost?: number;
  metadata?: Record<string, unknown>;
}

// What tracker.record() returns
interface CostRecord {
  id: string;                          // UUID v4
  timestamp: string;                   // ISO 8601
  tags: Tags;
  model: string;
  provider: string;
  inputTokens: number;
  outputTokens: number;
  totalTokens: number;
  cost: number;                        // USD
  metadata?: Record<string, unknown>;
}

// Per-million-token pricing
interface ModelPricing {
  input: number;   // USD per million input tokens
  output: number;  // USD per million output tokens
}

// Implement this to add a custom storage backend
interface StorageAdapter {
  append(records: CostRecord[]): Promise<void>;
  query(filters: QueryFilters): Promise<CostRecord[]>;
  purge(filters: QueryFilters): Promise<number>;
  close(): Promise<void>;
}

// Filters for query() and purge()
interface QueryFilters {
  from?: string;       // ISO 8601
  to?: string;         // ISO 8601
  tags?: Tags;
  models?: string[];
  providers?: string[];
}

License

MIT