@crediball/sdk
v0.19.0
Published
TypeScript SDK for Crediball — usage-based credit billing for AI apps.
Maintainers
Readme
@crediball/sdk
TypeScript SDK for Crediball — credits-based monetization for AI apps. Wrap the actions your app performs; Crediball auto-discovers them and you price them in the dashboard. No pricing, prices, or manifests in code.
npm install @crediball/sdkFastest start — npx @crediball/sdk init
npx @crediball/sdk initDetects your framework (Next.js / Vite / CRA), writes CREDIBALL_API_KEY (and, if you pass
it, the publishable key with the correct public-env prefix) into the right env file without
clobbering anything, and prints the two lines below plus the one thing only you can do: wrap
your actions. The API base URL already defaults to the hosted API, so there's nothing else to
configure. Pass --key cb_live_…, --publishable-key cb_pub_…, or --yes to run it
non-interactively.
Quick start (server-side)
Wrap the business action your app performs — not the AI call underneath it:
import crediball from "@crediball/sdk";
crediball.init(process.env.CREDIBALL_API_KEY!, {
baseUrl: process.env.CREDIBALL_API_URL, // defaults to the hosted API
});
// crediball.action() does four things in one call:
// 1. registers "summarize" if Crediball hasn't seen it (auto-discovery)
// 2. checks + deducts its credits BEFORE the work runs (live mode)
// 3. runs your callback and returns its result unchanged
// 4. lets the optional AI wrappers (below) enrich it with model/token telemetry
const summary = await crediball.action("summarize", "u123", () => summarizeDocument(pdf));Because billing lives at the action boundary, you can switch OpenAI → Anthropic, add another model call, or rewrite the internals of the callback without ever touching the billing code. That separation is the whole point.
Every app starts in sandbox: every action is observed, nothing is billed. Activate
the actions you want to charge for in the dashboard, set a credit cost, and flip the app
to live — then action/track consume credits from the given user. Add a new
action six months later and it simply appears in the dashboard, waiting for a price —
no config, no sync, no manifest.
Registering a user at signup? Use identify
track/action already register a user the first time they do something, but if
you'd rather have new users show up in the dashboard right at signup — including
ones who never end up doing anything billable — call identify there:
await crediball.identify(user.id);It's create-if-absent, so calling it again later (or letting track/action run
before it) is a safe no-op.
Just recording an action? Use track
When there's no result to wrap (fire-and-forget, or you bill at a coarser boundary),
call track directly — it's the same billing under the hood:
await crediball.track("generate_poem", "u123");API
| Function | Description |
|---|---|
| crediball.init(apiKey, { baseUrl?, fetch? }) | Initialize the shared client once at startup (server-side). |
| crediball.identify(userId, { initialCredits? }) | Register a user (e.g. at signup), create-if-absent. Grants the signup bonus, if configured. Optional — action/track register on first activity anyway. |
| crediball.action(action, userId?, fn, { metadata? }) | The happy path. Register-if-new, deduct credits before fn runs, run fn, return its result. AI wrappers inside fn enrich instead of billing again. |
| crediball.track(action, userId?, { metadata? }) | Record an action without wrapping a callback. Sandbox: observed. Live + activated + userId: bills its credits. |
| crediball.getClient() | The shared client, for the purchase helpers below. |
The shared client also exposes purchase/paywall helpers:
| Method | Description |
|---|---|
| topup({ userId, packageId?, amount?, metadata? }) | Add credits. Pass packageId (from listPackages()) to auto-resolve credits + price. |
| getBalance(userId) | Return the user's current balance. |
| listPackages({ userId? }) | Top-up options: { currency, packages, subscriptionPlans, customTopup, signupBonus }. |
| subscribe / cancelSubscription / getUserSubscription | Manage an end user's recurring subscription. |
Errors
InsufficientCreditsError— balance too low in live mode (catch it to show your paywall).CrediballApiError— any other non-2xx response, plus config/network failures (.status,.body,.message). Catch this too so a misconfiguredbaseUrl/key or an unreachable API surfaces as a clean message instead of an unhandled 500:
import { InsufficientCreditsError, CrediballApiError } from "@crediball/sdk";
try {
await crediball.track("generate_poem", "u123");
} catch (e) {
if (e instanceof InsufficientCreditsError) {/* show paywall / top-up UI */}
else if (e instanceof CrediballApiError) {/* config/network error — surface cleanly */}
else throw e;
}Optional: auto-instrument your AI calls — @crediball/sdk/ai
The /ai subpath is an enrichment layer on top of your actions. It wraps your
model(s)/client(s) once so every AI call underneath contributes provider, model,
latency and token telemetry — across the Vercel AI SDK and the raw OpenAI / Anthropic /
Google (Gemini) SDKs, plus a catch-all for anything else. It has no dependencies —
your AI libraries are detected structurally.
Use it two ways:
- Inside
crediball.action(...)(recommended): the wrappers enrich the action with model/token telemetry and never bill a second time —actionalready took the charge, up front. You get "which model/how many tokens didsummarizeuse?" in the dashboard for free, and billing stays anchored to the business action. - Standalone (no
actionaround them): the wrappers become the billing mechanism themselves — each wrapped call tracks its own event. Handy when the model call is the unit you price (per image, per transcription-minute), or as a low-touch way to cover a generic dispatcher without editing every call site.
Key actions by use-case, not by model. One model can back many actions (override the event per call); many models can back one action (share an event key). The model/provider/modality are recorded as metadata.
import crediball from "@crediball/sdk";
import { wrapOpenAI, wrapAnthropic, wrapGemini, crediballMiddleware, instrument, withUser }
from "@crediball/sdk/ai";
crediball.init(process.env.CREDIBALL_API_KEY!);
// Raw clients — wrap once; text, image, audio & video calls are all tracked.
const openai = wrapOpenAI(new OpenAI(), { event: "assistant" });
// Vercel AI SDK — one middleware covers every text provider:
const model = wrapLanguageModel({ model: openai_("gpt-4o"), middleware: crediballMiddleware({ event: "chat" }) });
await generateText({ model, prompt, providerOptions: { crediball: { event: `reader_turn_${mode}`, userId } } });
// Anything else (custom provider, AI SDK image/speech) — the catch-all:
const makeCover = instrument((p: string) => openai.images.generate({ model: "gpt-image-1", prompt: p }),
{ event: "cover_art", modality: "image" });
// Thread the end user through a request so raw-client calls bill them, no signature changes:
await withUser(session.userId, () => handleRequest(req));When a wrapper bills standalone, tracking fires before the model call, so in live mode an
out-of-credits user is stopped with InsufficientCreditsError before the expensive request
runs. embeddings/moderations are off by default (pass includeOptIn: true). A standalone
wrapper bills per model call, so one user action that fans out into several calls bills
several times — to bill once for the whole action, wrap it in crediball.action(...) (the
gate then fires up front, before any model call, and the wrappers inside only enrich).
Token usage is captured automatically: after a non-streaming text call, the wrappers read
the response's token counts and report them (a separate, non-billing call) so the dashboard
shows tokens per action/model. No extra code — tokens never affect billing. For a custom
provider, pass a tokens: (result) => ({ input, output, total }) extractor to instrument().
Principles
- Bill the action, not the AI call. Wrap the business action with
crediball.action(...); the AI wrappers are an optional telemetry layer on top. - No pricing in code. Crediball auto-discovers actions; set credit costs in the dashboard.
- Server-side only. Never ship your
cb_live_key to the browser. - Pair with
@crediball/reactfor drop-in, unbranded credit UI.
