TypeScript SDK Reference
Reference for every public symbol exported from @paygentjs/sdk. Anything not on this page (modules with a leading _, internal helpers) is implementation detail and may change between minor versions.
import {
Paygent,
paygentContext,
track,
getCurrentContext,
instrument,
wrap,
customProvider,
PaygentLimitExceeded,
checkGuard,
createDefaultRegistry,
ProviderRegistry,
OPENAI_PROVIDER,
ANTHROPIC_PROVIDER,
} from "@paygentjs/sdk";
import type {
PaygentOptions,
WrapOptions,
PlanConfig,
ModelCostRate,
ModelLimitConfig,
CurrentUsage,
BillingPeriod,
UserState,
GuardResult,
GateStatus,
GateEvent,
UsageEvent,
TokenInfo,
UsageSnapshot,
MaxTokensAdvice,
} from "@paygentjs/sdk";
// Framework integrations (subpath exports):
import { PaygentCallbackHandler } from "@paygentjs/sdk/integrations/langchain";
// pg.aiMiddleware() — Vercel AI SDK — is a method on the Paygent instance.
Requires Node 18+ (global fetch + AsyncLocalStorage). Ships dual ESM + CJS with types.
Lifecycle
Paygent.init(options?)
static init(options?: PaygentOptions): Promise<Paygent>
Creates and initializes the Paygent singleton, bootstrapping the backend connection and background sync. async — always await it.
| Option | Default | Description |
|---|---|---|
apiKey |
undefined |
Backend API key (pg_live_...). Omit for local/offline mode. |
baseUrl |
https://api.paygent.to |
Backend base URL. |
planConfig |
undefined |
A Partial<PlanConfig> applied to users until the backend serves a per-user one (or while it's unreachable). In local mode this is the effective plan for every user. |
storage |
"memory" |
Offline event cache. "sqlite" requires the optional better-sqlite3 peer. |
sqlitePath |
— | SQLite file path when storage: "sqlite". |
flushIntervalMs |
5000 |
Background flush cadence (ms). |
autoInstrument |
false |
Globally patch installed OpenAI/Anthropic SDKs so every client is metered without instrument(). Opt-in — it mutates shared prototypes; close() restores them. |
registry |
undefined |
Custom ProviderRegistry (advanced). |
const pg = await Paygent.init({ apiKey: process.env.PAYGENT_API_KEY });
pg.flush()
flush(): Promise<void>
Force-drains the event + gate-event queues to the backend. Call before a short-lived process exits so no events are lost.
pg.close()
close(): Promise<void>
Flushes, stops the background timer, and — if autoInstrument was used — restores the patched provider prototypes. Idempotent. This is the teardown call (the analog of the Python SDK's shutdown()).
Context
paygentContext(ctx, fn)
function paygentContext<T>(ctx: PaygentContext, fn: () => T): T
Runs fn with the given context active (via AsyncLocalStorage). Every instrumented LLM call inside — including across await — is attributed to ctx.userId. Returns whatever fn returns.
await paygentContext({ userId: "user_123" }, async () => {
await openai.chat.completions.create({ /* ... */ });
});
PaygentContext: { userId: string; sessionId?: string; plan?: string; metadata?: Record<string, unknown> }.
track(ctx, fn)
function track<A extends unknown[], R>(
ctx: PaygentContext | ((...args: A) => PaygentContext | undefined),
fn: (...args: A) => R,
): (...args: A) => R
Wraps a function so every invocation runs inside a context — the decorator-free analog of Python's @paygent_track. ctx may be a static object or a resolver computed from the arguments. If it resolves to no user, fn runs untracked (fail-open).
getCurrentContext()
function getCurrentContext(): PaygentContext | undefined
The active context, or undefined when none is set.
Instrumentation
pg.instrument(client)
instrument<T extends object>(client: T): T
Returns a Proxy-wrapped copy of a provider client (OpenAI / Anthropic). Calls made inside a paygentContext are guarded (may throw PaygentLimitExceeded) → executed → metered → synced. Calls with no active user pass through untouched. The returned object has the same surface as the original.
const openai = pg.instrument(new OpenAI());
pg.wrap(fn, options?)
wrap<T>(fn: () => Promise<T> | T, options?: WrapOptions): Promise<T>
Explicitly guard + meter a single call without wrapping the whole client. fn performs the actual request.
WrapOptions:
| Field | Description |
|---|---|
userId |
End-user id. Falls back to the active paygentContext user. |
model |
Model name for guard checks + cost. |
provider |
"openai" (default), "anthropic", or a custom ProviderDescriptor. |
stream |
true if the call returns a stream to wrap. |
messages |
Request messages — enables pre-call input-token estimation. |
maxTokens |
Caller's max_tokens — output bound for estimation + reservation. |
toolCalls, metadata |
Optional extras attached to the event. |
const res = await pg.wrap(
() => openai.chat.completions.create({ model: "gpt-4o", messages }),
{ userId: "user_123", model: "gpt-4o" },
);
autoInstrument (global patch)
Pass autoInstrument: true to init() to patch the installed openai / @anthropic-ai/sdk prototypes so every client instance is metered without instrument(). It must run before the .create() call. close() restores the originals. Opt-in because it mutates shared globals.
Callbacks
All return void; register as many as you like (they fire in order; one throwing doesn't stop the others).
pg.onSoftGate((g: GuardResult) => void) // approaching a limit (call still runs)
pg.onHardGate((g: GuardResult) => void) // over a limit; fires THEN PaygentLimitExceeded throws
pg.onUsage((e: UsageEvent) => void) // after every successful metered call
pg.onSessionStart((s: UserState) => void) // first time a user's state is loaded
pg.onSoftGate((g) => console.warn(g.message));
pg.onUsage((e) => metrics.add(e.costTotal));
Queries & pre-flight
pg.getUsage(userId)
getUsage(userId: string): UsageSnapshot
Reads the in-memory cache (synced with the backend). UsageSnapshot: { userId, totalCost, totalTokens, tokensByModel, costByModel }.
pg.getMaxTokens(userId, model, opts?)
getMaxTokens(
userId: string,
model: string,
opts?: { estimatedInputTokens?: number; messages?: unknown; hardCap?: number },
): Promise<MaxTokensAdvice>
Recommends the largest max_tokens that won't push the user past any hard gate. Pass messages to estimate input tokens automatically. Fail-closed: any error returns { maxTokens: 0, bindingLimit: "blocked", ... }. hardCap defaults to 4096.
MaxTokensAdvice: { maxTokens, bindingLimit, periodSpendRemaining, sessionSpendRemaining, modelTokensRemaining } where bindingLimit is "period_spend" | "session_spend" | "model_tokens" | "unbounded" | "blocked".
pg.precheck(userId, model?)
precheck(userId: string, model?: string | null): Promise<GuardResult>
Runs the guard a call would run — fires soft/hard callbacks and emits the gate-audit event — but never throws and makes no LLM call. Use it for pre-flight checks and as the primitive behind framework integrations.
pg.recordUsage(userId, info, opts?)
recordUsage(userId: string, info: TokenInfo, opts?: { metadata?: Record<string, unknown> }): void
Meter a call the SDK didn't intercept (the primitive framework callbacks use after the framework reports usage). TokenInfo: { model, inputTokens, outputTokens, totalTokens }.
Errors
PaygentLimitExceeded
class PaygentLimitExceeded extends Error {
readonly guardResult: GuardResult;
}
The only error Paygent throws — raised at a hard gate, before the LLM call runs. Everything else is fail-open. Catch it in your request handler and read err.guardResult.
try {
await openai.chat.completions.create({ /* ... */ });
} catch (err) {
if (err instanceof PaygentLimitExceeded) {
err.guardResult.gateReason; // "total_spend" | "session_spend" | "model_limit:gpt-4o"
err.guardResult.message;
err.guardResult.usagePct;
}
}
Framework integrations
PaygentCallbackHandler (LangChain.js)
import { PaygentCallbackHandler } from "@paygentjs/sdk/integrations/langchain";
new PaygentCallbackHandler(pg, { userId?, model?, metadata? })
A BaseCallbackHandler that guards on handleLLMStart (throws on hard gate) and meters on handleLLMEnd. Pass it via config.callbacks. Also covers LangGraph.js. Do not also instrument() the same client — the JS handler doesn't auto-deduplicate.
pg.aiMiddleware(options?) (Vercel AI SDK)
aiMiddleware(options?: { userId?: string; model?: string; metadata?: Record<string, unknown> }): LanguageModelMiddleware
A wrapLanguageModel middleware that guards + meters both generateText and streamText.
import { wrapLanguageModel } from "ai";
const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: pg.aiMiddleware({ userId }) });
Providers (advanced)
createDefaultRegistry() returns a ProviderRegistry with OPENAI_PROVIDER + ANTHROPIC_PROVIDER registered. customProvider(...) builds a ProviderDescriptor for wrap() when you need to meter a client the built-ins don't cover. Most apps never touch these.
Types
See the import block at the top of this page. Internal models are camelCase (inputTokens, costTotal, gateReason, periodTokensByModel, …) — the wire layer maps them to/from the backend's snake_case. Notable shapes:
GuardResult—{ status, gateReason, usagePct, currentValue, limitValue, message };statusis"ok" | "soft_gate" | "hard_gate".UsageEvent—{ id, userId, sessionId, timestamp, model, inputTokens, outputTokens, totalTokens, toolCalls, costTokens, costTools, costTotal, metadata, synced }.PlanConfig—{ maxSpendPerPeriod, maxSpendPerSession, softGateAt, hardGateAt, modelLimits, costRates, defaultCostRate, preCallEstimate, preCallBufferTokens, reservationSafetyFactor, ... }.
See also
- Quickstart — dual-language getting-started
- Frameworks — LangChain.js + Vercel AI SDK
- Callbacks & Events — the callback model
- Python SDK Reference — the Python counterpart