dexcost
TypeScript

API Reference

Complete reference for every public symbol exported from the dexcost TypeScript SDK, grouped by function.

Initialization

init()

function init(options?: TrackerOptions): CostTracker

Initialize the SDK. Call once at application startup. Creates a global CostTracker instance, instruments configured LLM providers, and starts the background EventPusher when a valid API key is present.

Throws an Error if called when a global instance already exists. Call close() or closeAsync() first to reset.

ParameterTypeDefaultDescription
optionsTrackerOptions{}Configuration object. See Configuration for all fields.

Returns the CostTracker created.

getTracker()

function getTracker(): CostTracker

Return the global CostTracker created by init(). Throws if init() has not been called.

track()

async function track<T>(
  opts: {
    taskType: string;
    customerId?: string;
    projectId?: string;
    metadata?: Record<string, unknown>;
    experimentId?: string;
    variant?: string;
  },
  fn: (task: TrackedTask) => Promise<T>
): Promise<T>

Module-level shorthand for getTracker().track(opts, fn). Groups all cost events recorded inside fn into a single task and returns the value resolved by fn.

ParameterTypeDescription
opts.taskTypestringIdentifier for the kind of task (e.g. "resolve_ticket").
opts.customerIdstringCustomer attribution.
opts.projectIdstringProject attribution.
opts.metadataRecord<string, unknown>Arbitrary metadata attached to the task.
opts.experimentIdstringOptional experiment identifier for A/B tracking.
opts.variantstringOptional variant label for the experiment.
fn(task: TrackedTask) => Promise<T>Async callback that receives the TrackedTask handle.

On clean completion the task status is "success". If fn throws, status is "failed" and all recorded events are still persisted.

flush()

async function flush(): Promise<void>

Force an immediate sync of buffered events to the Control Layer. No-op in local-only mode or if init() has not been called.

close()

function close(): void

Stop the background pusher, uninstrument all active providers, untrack HTTP, and close the SQLite buffer. Sets the global instance to null. Safe to call multiple times.

closeAsync()

async function closeAsync(): Promise<void>

Flush pending events, then stop the background pusher and release all resources. Prefer this over close() when guaranteed event delivery is required before process exit.


Tasks

CostTracker.track()

async track<T>(
  opts: {
    taskType: string;
    customerId?: string;
    projectId?: string;
    metadata?: Record<string, unknown>;
    experimentId?: string;
    variant?: string;
  },
  fn: (task: TrackedTask) => Promise<T>
): Promise<T>

Execute fn inside a tracked task context. Creates a new Task, runs fn within an AsyncLocalStorage context, ends the task on completion (or failure), and returns the value resolved by fn.

Nested track() calls automatically set parentTaskId on the inner task.

CostTracker.startTask()

startTask(opts?: {
  taskType?: string;
  customerId?: string;
  projectId?: string;
  metadata?: Record<string, unknown>;
  experimentId?: string;
  variant?: string;
}): TrackedTask

Manually start a task and return a TrackedTask handle. Use when callbacks do not fit your architecture (e.g., event-driven workers, Celery-style jobs). The caller must call TrackedTask.end() exactly once.

getCurrentTask()

function getCurrentTask(): Task | undefined

Return the Task currently active in the AsyncLocalStorage context, or undefined if no task is active.

runWithTask()

function runWithTask<T>(task: Task, fn: () => T): T

Execute fn with task as the active task context. All code inside fn (including async continuations) sees task via getCurrentTask().


Recording costs

TrackedTask.recordLlmCall()

recordLlmCall(
  provider: string,
  model: string,
  inputTokens: number,
  outputTokens: number,
  cost?: number,
  cachedTokens?: number,
  latencyMs?: number,
  options?: {
    costConfidence?: CostConfidence;
    pricingSource?: PricingSource;
    pricingVersion?: string;
    details?: Record<string, unknown>;
    errorType?: string;
  }
): CostEvent

Record an LLM call event. When cost is undefined, cost is auto-computed via PricingEngine from bundled LiteLLM pricing data.

ParameterTypeDefaultDescription
providerstringProvider name (e.g. "openai").
modelstringModel identifier (e.g. "gpt-4o").
inputTokensnumberNumber of input tokens.
outputTokensnumberNumber of output tokens.
costnumberundefinedCost in USD. undefined → auto-computed.
cachedTokensnumberundefinedCached input tokens. Receive a per-token discount if the model supports cache reads.
latencyMsnumberundefinedResponse latency in milliseconds.
options.costConfidenceCostConfidenceAuto-setOverride confidence level.
options.pricingSourcePricingSourceAuto-setOverride pricing source label.
options.pricingVersionstringAuto-setOverride pricing version hash.
options.detailsRecord<string, unknown>{}Extra metadata.
options.errorTypestringundefinedTransient error type ("rate_limit", "timeout", "5xx", "server_error", "connection_error"). Stored in details.error_type and used by heuristic retry detection.

TrackedTask.recordCost()

recordCost(
  service: string,
  costUsd: number,
  details?: Record<string, unknown>,
  eventType?: EventType,
  costConfidence?: CostConfidence,
  pricingSource?: PricingSource,
  pricingVersion?: string
): CostEvent

Record a non-LLM cost event.

ParameterTypeDefaultDescription
servicestringService name (e.g. "pinecone").
costUsdnumberCost in USD.
detailsRecord<string, unknown>{}Arbitrary extra metadata.
eventTypeEventType"external_cost"Must be "external_cost" or "compute_cost". Throws if another value is passed.
costConfidenceCostConfidence"exact"Confidence level of the cost figure.
pricingSourcePricingSource"manual"Source of the pricing data.
pricingVersionstringundefinedHash referencing the rate snapshot.

TrackedTask.recordUsage()

recordUsage(
  service: string,
  units?: number,
  details?: Record<string, unknown>
): CostEvent

Compute cost from the rate registry (units × registered_rate) and record an external_cost event.

ParameterTypeDefaultDescription
servicestringService name matching a registered rate.
unitsnumber1Number of units consumed.
detailsRecord<string, unknown>{}Arbitrary extra metadata.

Throws if no rate is registered for service.

TrackedTask.markRetry()

markRetry(
  reason: string,
  cost?: number,
  retryOf?: string
): CostEvent

Explicitly flag the current operation as a retry. Creates a retry_marker event and increments the task's retryCount and retryCostUsd aggregates.

ParameterTypeDefaultDescription
reasonstringWhy the retry occurred (e.g. "rate_limit", "timeout").
costnumber0Additional cost incurred by the retry.
retryOfstringundefinedeventId of the original event this retries.

TrackedTask.markNotRetry()

markNotRetry(eventId?: string): CostEvent | undefined

Override a false-positive retry detection. When eventId is undefined, the most recent retry event for this task is un-flagged. Returns the updated CostEvent, or undefined if no retry event was found.

TrackedTask.linkTrace()

linkTrace(provider: string, traceId: string): void

Link an external trace to this task. Stored under task.metadata._trace_links as { provider, trace_id } entries.

ParameterTypeDescription
providerstringObservability platform name (e.g. "langfuse", "langsmith").
traceIdstringTrace or run identifier from the external platform.
getTraceLinks(): Array<{ provider: string; trace_id: string }>

Return all linked traces for this task as an array of { provider, trace_id } objects.

TrackedTask.end()

end(status?: "success" | "failed"): void

Close a manually-started task, set its status, and persist aggregates. Must be called exactly once for tasks created via startTask(). Throws if called more than once.

TrackedTask.task

get task(): Task

The underlying Task data object.

TrackedTask.events

get events(): ReadonlyArray<CostEvent>

All CostEvent objects recorded against this task, in insertion order.


Instrumentation

CostTracker.instrument()

async instrument(name: string): Promise<void>

Activate the named instrument, dynamically importing the provider package and monkey-patching its LLM method. Idempotent — calling it twice for the same name is a no-op.

CostTracker.uninstrument()

uninstrument(name: string): void

Deactivate the named instrument, restoring the original library methods.

ALL_SUPPORTED_INSTRUMENTS

const ALL_SUPPORTED_INSTRUMENTS: readonly string[] = [
  "openai", "anthropic", "vercel-ai", "gemini", "bedrock", "cohere", "mcp"
]

The full list of SDK names accepted by instrument() and the autoInstrument option.

registerDomainRate()

function registerDomainRate(domain: string, costUsd: number, per?: string): void

Register a per-request rate for HTTP calls to domain. User-registered rates take precedence over catalog entries. per defaults to "request".

trackHttp()

function trackHttp(buffer?: EventBuffer): void

Patch globalThis.fetch and Node's http/https modules to intercept HTTP calls and auto-record external_cost events. Idempotent — safe to call multiple times.

untrackHttp()

function untrackHttp(): void

Restore globalThis.fetch and the Node http/https transports to their original values.

DexcostCallbackHandler

class DexcostCallbackHandler {
  constructor(tracker: CostTracker)
  handleLLMStart(serialized: Record<string, unknown>, prompts: string[], runId: string): void
  handleLLMEnd(output: Record<string, unknown>, runId: string): void
  handleLLMError(error: Error, runId: string): void
}

Duck-typed LangChain callback handler. Does not import @langchain/core. Attach to any LangChain chain or agent via the callbacks option.

createExpressMiddleware()

function createExpressMiddleware(
  tracker: CostTracker,
  options?: ExpressMiddlewareOptions
): (req: unknown, res: unknown, next: () => void) => void

Create an Express/Connect middleware that wraps each request in a tracked task and attaches TrackedTask to req.dexcostTask.

ExpressMiddlewareOptions:

FieldTypeDescription
customerIdFromstringDot-path into req to extract a customer ID.
projectIdFromstringDot-path into req to extract a project ID.
taskType(req) => stringFunction to derive the task type. Defaults to "METHOD /path".
skip(req) => booleanReturn true to skip tracking for a request.

trackBrowser()

async function trackBrowser<T>(
  page: unknown,
  fn: () => Promise<T>,
  options?: TrackBrowserOptions
): Promise<T>

Wrap Playwright work in a timed compute_cost event. Accepts any object with a .url property. The event is recorded whether or not fn throws.

TrackBrowserOptions:

FieldTypeDefaultDescription
ratePerMinutenumber0.01Cost in USD per minute of session wall-clock time.

Pricing & rates

PricingEngine

class PricingEngine {
  constructor()
  get pricingVersion(): string
  getCost(
    model: string,
    inputTokens: number,
    outputTokens: number,
    cachedTokens?: number,
    cacheCreationTokens?: number
  ): CostResult
  setCustomPricing(model: string, inputPer1k: number, outputPer1k: number): void
  setApiKey(key: string | undefined): void
  startBackgroundRefresh(endpoint: string, intervalMs?: number): void
  stopBackgroundRefresh(): void
}

Calculates LLM costs from model name and token counts. Loads bundled LiteLLM pricing data on construction.

PricingEngine.getCost()

ParameterTypeDefaultDescription
modelstringModel identifier. Fuzzy-matched against the bundled pricing map (e.g. "gpt-4o", "claude-3-5-sonnet-20241022").
inputTokensnumberNumber of input tokens.
outputTokensnumberNumber of output tokens.
cachedTokensnumber0Tokens served from cache (receive a discounted rate).
cacheCreationTokensnumber0Tokens written to cache (charge cache-creation rate).

Returns a CostResult.

PricingEngine.setCustomPricing()

Register custom per-token pricing for a model. Custom pricing takes precedence over the bundled data.

ParameterTypeDescription
modelstringExact model identifier.
inputPer1knumberInput cost per 1,000 tokens in USD.
outputPer1knumberOutput cost per 1,000 tokens in USD.

PricingEngine.pricingVersion

string — SHA-256 hash (first 12 characters) of the currently loaded pricing data, for reproducibility.

PricingEngine.startBackgroundRefresh()

Start a background timer that calls refreshFromServer(endpoint) immediately and then every intervalMs milliseconds (default: 86,400,000 — 24 hours). Requires the API key to be set via setApiKey().

CostResult

interface CostResult {
  costUsd: number;
  pricingSource: PricingSource;
  costConfidence: "computed" | "unknown";
  pricingVersion: string;
}

Returned by PricingEngine.getCost().

FieldDescription
costUsdCalculated cost in USD.
pricingSource"litellm", "custom", or "unknown".
costConfidence"computed" when a pricing entry was found, "unknown" otherwise.
pricingVersionHash of the pricing data used.

RateRegistry

class RateRegistry {
  register(service: string, per: string, costUsd: number): void
  get(service: string): RateEntry | undefined
  get rates(): Record<string, RateEntry>
  get pricingVersion(): string
  load(path: string): void
  export(path: string): void
}

Registry of per-service cost rates for non-LLM services.

RateRegistry.register()

Register a per-unit cost rate for service. Calling it again for the same service overwrites the existing rate.

RateRegistry.get()

Return the RateEntry for service, or undefined if not registered.

RateRegistry.rates

A snapshot of all registered rates as a Record<string, RateEntry>.

RateRegistry.pricingVersion

string — deterministic SHA-256 hash (first 12 characters) of all registered rates, sorted by service name.

RateRegistry.load()

Load rates from a YAML file. Requires js-yaml. Expected keys: rates.<service>.per and rates.<service>.cost_usd.

RateRegistry.export()

Export current rates to a YAML file, sorted by service name. Requires js-yaml.

RateEntry

interface RateEntry {
  service: string;
  per: string;
  costUsd: number;
}

CostTracker.registerRate()

registerRate(service: string, per: string, costUsd: number): void

Register a per-unit rate on the tracker's internal RateRegistry. Convenience wrapper for tracker.rateRegistry.register(...).

CostTracker.getRate()

getRate(service: string): number | undefined

Return the per-unit cost in USD for service, or undefined if not registered.


Types

TrackerOptions

interface TrackerOptions {
  apiKey?: string;
  batchSize?: number;
  flushIntervalMs?: number;
  redactFields?: string[];
  hashCustomerId?: boolean;
  autoInstrument?: string[];
  dbPath?: string;
  environment?: string;
  enableRetryHeuristics?: boolean;
  retryHeuristicWindow?: number;
  retryHeuristicThreshold?: number;
  storage?: "local" | "cloud";
  trackHttp?: boolean;
  serviceCatalogUrl?: string;
}

Configuration options passed to init() or the CostTracker constructor. See Configuration for field descriptions.

DexcostContext

interface DexcostContext {
  customerId?: string;
  projectId?: string;
  metadata?: Record<string, unknown>;
  agent?: string;
}

Ambient attribution context stored by setContext() in an AsyncLocalStorage store.

setContext() / getContext() / clearContext()

function setContext(ctx: DexcostContext): void
function getContext(): DexcostContext | undefined
function clearContext(): void

Manage the ambient DexcostContext for the current async execution chain. setContext() uses AsyncLocalStorage.enterWith() (Node 18+). clearContext() replaces the store with an empty object.

Task

interface Task {
  taskId: string;
  taskType: string;
  status: TaskStatus;
  startedAt: Date;
  endedAt?: Date;
  metadata: Record<string, unknown>;
  customerId?: string;
  projectId?: string;
  parentTaskId?: string;
  experimentId?: string;
  variant?: string;
  llmCostUsd: number;
  externalCostUsd: number;
  computeCostUsd: number;
  totalCostUsd: number;
  totalInputTokens: number;
  totalOutputTokens: number;
  totalCachedTokens: number;
  retryCount: number;
  retryCostUsd: number;
  failureCount: number;
  schemaVersion: string;
}

The data object persisted to SQLite for each tracked business task.

FieldDescription
taskIdUUID string — unique task identifier.
taskTypeKind of task (e.g. "resolve_ticket").
status"pending", "success", or "failed".
startedAtUTC Date when the task was created.
endedAtUTC Date when the task was closed, or undefined.
metadataOpen record for caller-defined context, including _trace_links.
customerIdCustomer attribution.
projectIdProject attribution.
parentTaskIdSet automatically for nested tasks.
experimentIdOptional experiment identifier.
variantOptional variant label for the experiment.
llmCostUsdSum of llm_call event costs.
externalCostUsdSum of external_cost event costs.
computeCostUsdSum of compute_cost event costs.
totalCostUsdSum of all event costs.
totalInputTokensAggregate input tokens across all LLM calls.
totalOutputTokensAggregate output tokens across all LLM calls.
totalCachedTokensAggregate cached tokens across all LLM calls.
retryCountNumber of events flagged as retries.
retryCostUsdSum of retry event costs.
failureCountNumber of times the task was ended with "failed".
schemaVersion"1" — Dexcost Standard Event Schema version.

CostEvent

interface CostEvent {
  eventId: string;
  taskId: string;
  eventType: EventType;
  occurredAt: Date;
  costUsd: number;
  costConfidence: CostConfidence;
  pricingSource?: PricingSource;
  pricingVersion?: string;
  provider?: string;
  model?: string;
  inputTokens?: number;
  outputTokens?: number;
  cachedTokens?: number;
  latencyMs?: number;
  serviceName?: string;
  isRetry: boolean;
  retryReason?: string;
  retryOf?: string;
  details: Record<string, unknown>;
  schemaVersion: string;
}

A single cost event within a task.

FieldDescription
eventIdUUID string — unique event identifier.
taskIdUUID of the task this event belongs to.
eventType"llm_call", "external_cost", "compute_cost", or "retry_marker".
occurredAtUTC Date of the event.
costUsdCost in USD as a number.
costConfidence"exact", "computed", "estimated", or "unknown".
pricingSourceSource of pricing data, or undefined.
pricingVersionHash of the pricing snapshot, or undefined.
providerLLM provider name for llm_call events, or undefined.
modelModel identifier for llm_call events, or undefined.
inputTokensInput tokens, or undefined for non-LLM events.
outputTokensOutput tokens, or undefined.
cachedTokensCached tokens, or undefined.
latencyMsResponse latency in milliseconds, or undefined.
serviceNameService name for non-LLM events, or undefined.
isRetrytrue when this event is flagged as a retry.
retryReasonWhy the retry occurred, or undefined.
retryOfeventId of the original event, or undefined.
detailsArbitrary extra metadata.
schemaVersion"1" — Dexcost Standard Event Schema version.

TaskStatus

type TaskStatus = "pending" | "success" | "failed"

EventType

type EventType = "llm_call" | "external_cost" | "compute_cost" | "retry_marker"

CostConfidence

type CostConfidence = "exact" | "computed" | "estimated" | "unknown"

PricingSource

type PricingSource =
  | "litellm"
  | "tokencost"
  | "provider_response"
  | "manual"
  | "custom"
  | "rate_registry"
  | "service_catalog"
  | "unknown"

ResolvedConfig

interface ResolvedConfig {
  apiKey?: string;
  keyType?: KeyType;
  isSandbox: boolean;
  storageMode: StorageMode;
}

Resolved SDK configuration returned by resolveConfig().

FieldDescription
apiKeyThe effective API key (explicit arg or DEXCOST_API_KEY env var).
keyType"live", "test", or undefined when no key.
isSandboxtrue when the key starts with dx_test_.
storageMode"cloud" when a valid key is present and storage is not "local", otherwise "local".

KeyType / StorageMode

type KeyType = "live" | "test"
type StorageMode = "cloud" | "local"

InvalidAPIKeyError

class InvalidAPIKeyError extends Error

Thrown by validateApiKey() when an API key does not start with dx_live_ or dx_test_.

validateApiKey()

function validateApiKey(key: string | undefined | null): KeyType | undefined

Validate API key format. Returns "live", "test", or undefined when key is undefined/null. Throws InvalidAPIKeyError for any other non-empty value.

resolveConfig()

function resolveConfig(apiKey?: string, storage?: StorageMode): ResolvedConfig

Resolve the effective API key and storage mode. Reads DEXCOST_API_KEY from the environment when apiKey is not passed explicitly and storage is not "local".

HeuristicMatch

interface HeuristicMatch {
  isRetry: boolean;
  confidence: number;
  matchedEventId: string | undefined;
  reason: string;
}

Result returned by RetryHeuristicEngine.check().

FieldDescription
isRetrytrue when the event is likely a retry.
confidenceConfidence score from 0.0 to 1.0.
matchedEventIdeventId of the failed predecessor, if matched.
reason"heuristic" when a match is found, "" otherwise.

RetryHeuristicEngine

class RetryHeuristicEngine {
  constructor(windowSeconds?: number, threshold?: number)
  get windowSeconds(): number
  get threshold(): number
  check(event: CostEvent): HeuristicMatch
  record(event: CostEvent): void
}

Sliding-window engine that detects probable retry events. check(event) determines whether an incoming event looks like a retry of a recently failed call (same model, transient error, within window). Call record(event) after check() to add the event to the window.

TRANSIENT_ERRORS (exported Set<string>): rate_limit, timeout, 5xx, server_error, connection_error.

ERROR_LIKELIHOODS (exported Record<string, number>): base likelihood per transient error type, before time-decay is applied.

TrackedOpenAI / TrackedAnthropic

class TrackedOpenAI {
  constructor(opts?: { client?: unknown; tracker?: CostTracker; pricing?: PricingEngine })
  get chat(): { completions: { create: (...args: unknown[]) => Promise<unknown> } }
}

class TrackedAnthropic {
  constructor(opts?: { client?: unknown; tracker?: CostTracker; pricing?: PricingEngine })
  get messages(): { create: (...args: unknown[]) => Promise<unknown> }
}

Thin wrapper classes around the OpenAI and Anthropic clients. Alternative to auto-instrumentation — no monkey-patching. Each create() call records an llm_call event automatically when running inside an active task context. openai and @anthropic-ai/sdk are optional peer dependencies; missing packages are reported with an actionable error message.

ServiceCatalog

class ServiceCatalog {
  constructor()
  lookup(url: string): ServiceEntry | undefined
  extractCost(
    entry: ServiceEntry,
    headers: Headers,
    body: unknown
  ): CostExtractionResult
  refreshFromUrl(url: string): Promise<void>
}

Manages the bundled service domain-to-pricing catalog. lookup(url) returns a ServiceEntry for the URL's hostname, or undefined. extractCost derives cost from the entry using response headers and/or body. refreshFromUrl downloads a replacement catalog JSON from a remote URL.

SessionManager

class SessionManager

Manages auto-created session tasks for LLM calls that occur outside an explicit track() context. getSessionManager() returns the global instance wired to the HTTP adapter.

EventBuffer

class EventBuffer {
  constructor(dbPath?: string)
  addEvent(event: CostEvent): void
  upsertTask(task: Task): void
  getAllEvents(): CostEvent[]
  getAllTasks(): Task[]
  getPendingEvents(limit: number): CostEvent[]
  queryEvents(taskId: string): CostEvent[]
  markSynced(eventIds: string[]): void
  close(): void
}

SQLite-backed buffer for CostEvent and Task records. Persists to ~/.dexcost/buffer.db by default. Pass dbPath to override.

EventPusher

class EventPusher {
  constructor(buffer: EventBuffer, options: TrackerOptions)
  start(): void
  stop(): void
  flush(): Promise<void>
}

Background worker that batches buffered events and pushes them to the Control Layer. Started automatically by CostTracker when storageMode is "cloud" and dev mode is off.

Serialisation helpers

function createTask(overrides: Partial<Task> & { taskId: string }): Task
function createCostEvent(overrides: Partial<CostEvent> & { eventId: string; taskId: string }): CostEvent
function taskToDict(task: Task): Record<string, unknown>
function eventToDict(event: CostEvent): Record<string, unknown>
function taskFromDict(data: Record<string, unknown>): Task
function eventFromDict(data: Record<string, unknown>): CostEvent

createTask and createCostEvent apply defaults for all optional fields. taskToDict / eventToDict serialise to JSON-safe dictionaries matching the Dexcost Standard Event Schema v1 (snake_case keys, costs serialised as strings). taskFromDict / eventFromDict are the inverse operations.

Security helpers

function redactDict(obj: Record<string, unknown>, fields: string[]): Record<string, unknown>
function hashValue(value: string): string
function enforceMetadataLimit(metadata: Record<string, unknown>): Record<string, unknown>

redactDict removes the named keys from a copy of obj. hashValue returns a hex SHA-256 digest. enforceMetadataLimit truncates metadata to the maximum allowed size.

isDevMode()

function isDevMode(): boolean

Returns true when dev mode is active (enabled by environment: "development" or DEXCOST_ENV=development).

validate()

function validate(data: unknown): boolean

Validate that data conforms to the Dexcost Standard Event Schema v1.

lambdaCost()

function lambdaCost(opts: {
  durationMs: number;
  memorySizeMb: number;
  requests?: number;
  region?: string;
}): LambdaCostResult

Calculate the AWS Lambda cost for a given invocation profile.

LambdaCostResult:

FieldTypeDescription
costUsdnumberTotal cost in USD.
detailsLambdaCostDetailsBreakdown of compute and request charges.

getSupportedRegions() returns the list of AWS regions for which Lambda pricing is bundled.

On this page