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

@velobaseai/billing

v1.2.0

Published

Velobase Billing SDK for JavaScript/TypeScript

Readme

@velobaseai/billing

Official Velobase Billing SDK for JavaScript and TypeScript.

  • Zero runtime dependencies — uses native fetch
  • Works with Node.js 18+, Deno, Bun, and Cloudflare Workers
  • ESM and CommonJS dual build with full TypeScript declarations
  • Automatic retries with exponential backoff

Installation

npm install @velobaseai/billing
# or
pnpm add @velobaseai/billing
# or
yarn add @velobaseai/billing

Quick Start

import Velobase from '@velobaseai/billing';

const vb = new Velobase({ apiKey: 'vb_live_xxx' });

// 1. Deposit funds to a customer in USD cents (creates the customer if new)
const deposit = await vb.customers.deposit({
  customerId: 'user_123',
  amountCents: 500, // $5.00
});

// 2. Check balance
const customer = await vb.customers.get('user_123');
console.log(customer.wallets.default.available); // 1000

// 3. Generate transactionId once and persist before freezing
const transactionId = `user_123_${crypto.randomUUID()}`;

// 4. Freeze credits before doing work
const freeze = await vb.billing.freeze({
  customerId: 'user_123',
  amount: 50,
  transactionId,
});

// 5a. Job succeeded — consume (supports partial)
const consume = await vb.billing.consume({
  transactionId,
  actualAmount: 32, // only charge 32, return 18
});

// 5b. Or if the job failed — unfreeze to return all
const unfreeze = await vb.billing.unfreeze({ transactionId });

How It Works

Velobase Billing uses a freeze-then-consume pattern to safely manage credits:

deposit → freeze → consume   (normal flow)
                 → unfreeze  (failure/cancellation)

It also supports a direct deduct pattern for immediate deduction without freezing:

deposit → deduct  (immediate deduction)
  1. Deposit — Add funds to a customer's wallet in USD cents. Creates the customer automatically on first deposit. Supports wallet/source for balance categories, and startsAt/expiresAt for time-limited credits. creditType is kept only as a deprecated alias for wallet.
  2. Freeze — Pre-authorize a credits amount before performing work. The frozen credits are deducted from available but not yet used. Each freeze is identified by a unique transactionId you provide.
  3. Consume — After the work is done, settle the frozen amount. You can pass actualAmount to charge less than what was frozen; the difference is automatically returned.
  4. Unfreeze — If the work fails or is cancelled, release the full frozen amount back to the customer.
  5. Deduct — Directly deduct credits from a customer without freezing first. Useful for immediate charges.
  6. Ledger — Query a customer's transaction history with filtering and cursor-based pagination.
  7. Gateway usage / receipts — Query LLM gateway usage rows and per-transaction receipts for reconciliation.

All write operations are idempotent — repeating the same transactionId (freeze/consume/unfreeze/deduct) or idempotencyKey (deposit) returns the original result without double-charging.

Configuration

const vb = new Velobase({
  apiKey: 'vb_live_xxx',             // Required. Your Velobase API key.
  baseUrl: 'https://api.velobase.io', // Optional. Override the API endpoint.
  timeout: 30000,                     // Optional. Request timeout in ms (default: 30s).
  maxRetries: 2,                      // Optional. Retry count on 5xx/network errors (default: 2).
});

When baseUrl is omitted, the SDK defaults to https://api.velobase.io. Development and staging environments must pass their endpoint explicitly; SDK defaults are always production-safe.

Usage Examples

Point LLM traffic at the Velobase gateway

The billing SDK does not wrap LLM calls. Use your LLM SDK with the Velobase gateway base URL, pass your project API key as the upstream API key, and attach the customer/trace headers for implicit per-call billing.

import { createAnthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import Velobase, { GATEWAY_HEADERS } from '@velobaseai/billing';

const gatewayUrl = 'https://api.velobase.io';
const gatewayApiKey = process.env.VELOBASE_GATEWAY_API_KEY!;
const customerId = 'user_123';
const traceId = 'turn_abc';

const anthropic = createAnthropic({
  baseURL: `${gatewayUrl}/v1`,
  apiKey: gatewayApiKey,
  headers: {
    [GATEWAY_HEADERS.customer]: customerId,
    [GATEWAY_HEADERS.traceId]: traceId,
  },
});

const openai = createOpenAI({
  baseURL: `${gatewayUrl}/v1`,
  apiKey: gatewayApiKey,
  headers: {
    [GATEWAY_HEADERS.customer]: customerId,
    [GATEWAY_HEADERS.traceId]: traceId,
  },
});

const vb = new Velobase({ apiKey: gatewayApiKey, baseUrl: gatewayUrl });
const usage = await vb.usage.list({ traceId });

Deposit with idempotency

// Safe to retry — the second call returns the same result without double-charging
const result = await vb.customers.deposit({
  customerId: 'user_123',
  amountCents: 500, // $5.00
  idempotencyKey: 'order_abc_payment',
  description: 'Purchase, $5.00',
});

console.log(result.addedAmountUsd);     // 5
console.log(result.isIdempotentReplay); // false on first call, true on retries

Deposit with wallet and expiry

const result = await vb.customers.deposit({
  customerId: 'user_123',
  amountCents: 1000, // $10.00
  wallet: 'bonus',
  source: 'annual_campaign',
  startsAt: '2025-01-01T00:00:00Z',
  expiresAt: '2025-12-31T23:59:59Z',
  description: 'Annual bonus, $10.00',
});
console.log(result.wallet); // 'bonus'
console.log(result.source); // 'annual_campaign'
console.log(result.startsAt);  // '2025-01-01T00:00:00.000Z'
console.log(result.expiresAt); // '2025-12-31T23:59:59.000Z'

Deposit with customer metadata

const result = await vb.customers.deposit({
  customerId: 'user_123',
  amountCents: 1000,
  name: 'Alice',
  email: '[email protected]',
  metadata: { plan: 'pro', source: 'stripe' },
});

Full billing flow (freeze-then-consume)

const CUSTOMER = 'user_123';

// Generate transactionId once and persist before freezing
const transactionId = `${CUSTOMER}_${crypto.randomUUID().replace(/-/g, '')}`;

// Check balance before starting
const before = await vb.customers.get(CUSTOMER);
console.log('Available:', before.wallets.default.available);

// Freeze the estimated cost
await vb.billing.freeze({
  customerId: CUSTOMER,
  amount: 100,
  transactionId,
  businessType: 'TASK',
  description: '1080p video, ~60s',
});

// ... do the work ...

// Settle with the actual cost (partial consumption)
const result = await vb.billing.consume({ transactionId, actualAmount: 73 });
console.log('Charged:', result.consumedAmount);  // 73
console.log('Returned:', result.returnedAmount); // 27

// Verify final balance
const after = await vb.customers.get(CUSTOMER);
console.log('Available:', after.wallets.default.available);

Direct deduct (without freezing)

const CUSTOMER = 'user_123';
const transactionId = 'api_call_001';

const result = await vb.billing.deduct({
  customerId: CUSTOMER,
  amount: 5,
  transactionId,
  businessType: 'TASK',
  description: 'API call charge',
});
console.log('Deducted:', result.deductedAmount); // 5
console.log('At:', result.deductedAt);

Query gateway usage and receipts

const usage = await vb.usage.list({
  customerId: 'user_123',
  traceId: 'turn_abc',
});

for (const row of usage.data) {
  console.log(row.transactionId, row.customerCostCredits, row.status);
}

const receipt = await vb.receipts.get(usage.data[0].transactionId);
console.log(receipt.cost.credits, receipt.balance.credits);

Query transaction ledger

// List all ledger entries (default limit=20)
const ledger = await vb.customers.ledger('user_123');
for (const entry of ledger.items) {
  console.log(entry.operationType, entry.amount, entry.wallet, entry.source, entry.createdAt);
}
console.log('Total:', ledger.totalCount);

// Filter by operation type
const grants = await vb.customers.ledger('user_123', { operationType: 'GRANT' });

// Filter by transactionId
const txnEntries = await vb.customers.ledger('user_123', { transactionId: 'job_abc' });

// Paginate with cursor
const page1 = await vb.customers.ledger('user_123', { limit: 10 });
if (page1.hasMore) {
  const page2 = await vb.customers.ledger('user_123', { limit: 10, cursor: page1.nextCursor });
}

Customer balance structure

const customer = await vb.customers.get('user_123');

// Wallets are keyed by wallet/category name.
const defaultWallet = customer.wallets.default;
defaultWallet.total;     // total deposited
defaultWallet.used;      // total consumed
defaultWallet.frozen;    // currently frozen (pending)
defaultWallet.available; // total - used - frozen

// Sources inside each wallet preserve validity windows.
for (const source of defaultWallet.sources) {
  console.log(source.source);      // 'default', 'stripe_checkout', etc.
  console.log(source.available);
  console.log(source.startsAt);    // null or ISO date string
  console.log(source.expiresAt);   // null or ISO date string
}

API Reference

vb.customers.deposit(params): Promise<DepositResponse>

Deposit funds. Creates the customer if they don't exist.

| Parameter | Type | Required | Description | |---|---|---|---| | customerId | string | Yes | Your unique customer identifier | | amountCents | number | One of amount fields | USD cents to deposit, e.g. 500 = $5.00 | | amountCredits | number | One of amount fields | Deprecated raw-credits deposit alias | | amount | number | One of amount fields | Deprecated raw-credits deposit alias | | wallet | string | No | Wallet/category to credit. Defaults to "default" on server. | | source | string | No | Optional source label for this grant. Defaults to "default" on server. | | creditType | string | No | Deprecated alias for wallet, kept for compatibility. | | startsAt | string | No | ISO datetime string. When the credits become active. | | expiresAt | string | No | ISO datetime string. When the credits expire. Must be after startsAt. | | idempotencyKey | string | No | Prevents duplicate deposits on retry | | name | string \| null | No | Customer display name | | email | string \| null | No | Customer email | | metadata | object | No | Arbitrary key-value metadata | | description | string | No | Description for the deposit |

Returns: { customerId, accountId, wallet, source, totalAmountUsd, addedAmountUsd, totalAmount, addedAmount, startsAt, expiresAt, recordId, isIdempotentReplay }

vb.customers.get(customerId): Promise<CustomerResponse>

Retrieve a customer's balance and account details.

Returns: { id, name, email, metadata, balance, accounts, createdAt }

vb.customers.ledger(customerId, params?): Promise<LedgerResponse>

Query a customer's transaction history with filtering and cursor-based pagination.

| Parameter | Type | Required | Description | |---|---|---|---| | customerId | string | Yes | Customer identifier (positional) | | params.limit | number | No | Page size (1–100, default 20) | | params.cursor | string | No | Cursor from a previous nextCursor for pagination | | params.operationType | string | No | Filter by operation type: FREEZE, CONSUME, UNFREEZE, GRANT, EXPIRE | | params.transactionId | string | No | Filter by transaction ID |

Returns: { items: LedgerEntry[], totalCount, hasMore, nextCursor }

Each LedgerEntry has: id, operationType, amount, wallet, source, transactionId, businessType, description, accountId, status, createdAt

vb.billing.freeze(params): Promise<FreezeResponse>

Freeze credits before performing work.

| Parameter | Type | Required | Description | |---|---|---|---| | customerId | string | Yes | Customer identifier | | amount | number | Yes | Credits to freeze (must be > 0). 1 credit = 1 micro-USD. | | transactionId | string | Yes | Your unique ID for this operation (idempotency key) | | businessType | BusinessType | No | Business category. See businessType for accepted values. | | description | string | No | Human-readable description |

Returns: { transactionId, frozenAmount, freezeDetails, isIdempotentReplay }

vb.billing.consume(params): Promise<ConsumeResponse>

Settle a frozen amount. Supports partial consumption.

| Parameter | Type | Required | Description | |---|---|---|---| | transactionId | string | Yes | The transactionId from the freeze | | actualAmount | number | No | Actual amount to charge. Defaults to full frozen amount. |

Returns: { transactionId, consumedAmount, returnedAmount, consumeDetails, consumedAt, isIdempotentReplay }

vb.billing.unfreeze(params): Promise<UnfreezeResponse>

Release a frozen amount back to the customer.

| Parameter | Type | Required | Description | |---|---|---|---| | transactionId | string | Yes | The transactionId from the freeze |

Returns: { transactionId, unfrozenAmount, unfreezeDetails, unfrozenAt, isIdempotentReplay }

vb.billing.deduct(params): Promise<DeductResponse>

Directly deduct credits without freezing first. Useful for immediate charges.

| Parameter | Type | Required | Description | |---|---|---|---| | customerId | string | Yes | Customer identifier | | amount | number | Yes | Credits to deduct (must be > 0). 1 credit = 1 micro-USD. | | transactionId | string | Yes | Your unique ID for this operation (idempotency key) | | businessType | BusinessType | No | Business category. See businessType for accepted values. | | description | string | No | Human-readable description |

Returns: { transactionId, deductedAmount, deductDetails, deductedAt, isIdempotentReplay }

vb.usage.list(params?): Promise<UsageListResponse>

List LLM gateway usage rows for the current project. This endpoint is intended for reconciliation and trace-level reporting after LLM traffic has gone through the Velobase gateway.

| Parameter | Type | Required | Description | |---|---|---|---| | customerId | string | No | Filter by X-Velobase-Customer | | model | string | No | Filter by public gateway model id | | transactionId | string | No | Filter by gateway transaction id | | traceId | string | No | Filter by X-Velobase-Trace-Id | | cursor | string | No | Cursor from a previous nextCursor | | limit | number | No | Page size |

Returns: { object: "list", data: UsageListItem[], hasMore, nextCursor }

vb.receipts.get(transactionId): Promise<ReceiptResponse>

Retrieve one LLM gateway billing receipt by transaction id.

Returns: { object: "billing.receipt", transactionId, status, customerId, traceId, model, usage, cost, frozen, balance, upstream, created }

transactionId

transactionId uniquely identifies one billing operation (freeze → consume/unfreeze cycle, or a single deduct) and acts as its idempotency key. The server uses it to prevent double-charging on retries.

Recommended format: {customerId}_{uuid}

// Generate once per billing operation, then persist it
const transactionId = `${customerId}_${crypto.randomUUID().replace(/-/g, '')}`;
// e.g. "user_123_a3f8c21d4e0b4a9f8c1d2e3f4a5b6c7d"

Rules:

  • Generate once and store — create the ID before calling freeze() or deduct(), save it to your database, and reuse the same value on retries
  • Never regenerate at the call site — calling crypto.randomUUID() inside freeze() produces a different ID on every attempt, breaking idempotency
  • Unique within your project — two different billing operations must not share the same transactionId
// Wrong — new UUID on every call, idempotency broken on retry
await vb.billing.freeze({
  customerId,
  amount: 50,
  transactionId: `${customerId}_${crypto.randomUUID()}`, // ❌ regenerated each time
});

// Correct — UUID generated once and persisted before calling freeze
const transactionId = await db.getOrCreateTransactionId(operationId, customerId);
// e.g. returns existing ID or stores `${customerId}_${crypto.randomUUID()}` on first call

await vb.billing.freeze({ customerId, amount: 50, transactionId });
// Safe to retry — same transactionId returns the original result
await vb.billing.freeze({ customerId, amount: 50, transactionId });

businessType

businessType is an optional field on freeze() and deduct() that categorises the billing operation for analytics and reconciliation. The SDK validates the value client-side before sending the request.

Accepted values:

| Value | Description | |---|---| | UNDEFINED | Default / unclassified (server default when omitted) | | TASK | Async task execution (e.g. video generation, image processing) | | ORDER | One-time purchase or order fulfilment | | MEMBERSHIP | Membership plan credit grant | | SUBSCRIPTION | Subscription renewal credit grant | | FREE_TRIAL | Free-trial credit grant | | ADMIN_GRANT | Manually granted credits by an admin |

Passing an unrecognised value throws an Error immediately — before any network request is made.

import Velobase, { type BusinessType } from '@velobaseai/billing';

const vb = new Velobase({ apiKey: 'vb_live_xxx' });

await vb.billing.freeze({
  customerId: 'user_123',
  amount: 50,
  transactionId: 'job_abc',
  businessType: 'TASK',        // ✅ IDE autocomplete + client-side validation
});

await vb.billing.freeze({
  customerId: 'user_123',
  amount: 50,
  transactionId: 'job_abc',
  businessType: 'INVALID_VAL', // ❌ throws Error before making a network call
});

Error Handling

All API errors throw typed exceptions that extend VelobaseError:

import {
  VelobaseError,
  VelobaseAuthenticationError,
  VelobaseValidationError,
  VelobaseNotFoundError,
} from '@velobaseai/billing';

try {
  await vb.billing.freeze({
    customerId: 'user_123',
    amount: 999999,
    transactionId: 'job_xyz',
  });
} catch (err) {
  if (err instanceof VelobaseValidationError) {
    // 400 — bad request or insufficient balance
    console.error(err.message); // "insufficient balance"
  } else if (err instanceof VelobaseAuthenticationError) {
    // 401 — invalid or missing API key
  } else if (err instanceof VelobaseNotFoundError) {
    // 404 — customer not found
  } else if (err instanceof VelobaseError) {
    // catch-all for other API errors
    console.error(err.status, err.type, err.message);
  }
}

| Error Class | HTTP Status | When | |---|---|---| | VelobaseAuthenticationError | 401 | Invalid or missing API key | | VelobaseValidationError | 400 | Bad params, insufficient balance | | VelobaseNotFoundError | 404 | Customer or resource not found | | VelobaseConflictError | 409 | Conflicting operation | | VelobaseInternalError | 500 | Server-side error (auto-retried) |

Retries

The SDK automatically retries on 5xx errors and network failures with exponential backoff (500ms, 1s, 2s..., capped at 5s). Retries are safe because all Velobase write operations are idempotent.

4xx errors (validation, auth, not found) are never retried.

CommonJS

const { Velobase } = require('@velobaseai/billing');

const vb = new Velobase({ apiKey: 'vb_live_xxx' });

License

MIT