Billwave
SDK Reference

track()

Record usage for metered billing

track()

Record usage events for metered billing. Use this to track API calls, storage, messages, or any countable resource.

Signature

await billwave.track({
  customer: string,    // Your internal user ID
  feature: string,     // Feature key being used
  value?: number,      // Amount to track (default: 1)
  entity?: string,     // Optional entity scope (seat, workspace, org)
  customerData?: {     // Optional: auto-create customer
    email: string,
    name?: string,
    metadata?: Record<string, unknown>,
  },
  metadata?: object,   // Optional event metadata
}): Promise<TrackResult>

Parameters

ParameterTypeRequiredDescription
customerstring✅Your identifier for the customer (e.g. your user or workspace ID). Also accepts the customer's email or internal ID.
featurestring✅Feature slug (or ID) to record usage against
valuenumber-Amount to add. Default 1. Must be >= 0.
entitystring-Scope usage to an entity (seat, workspace…). The entity must already exist.
customerDataobject-Creates the customer on first use if it doesn't exist. See below.
metadataobject-Free-form event metadata stored with the usage record (auditing, debugging)

customerData — create on first use

Customers don't need to be created up front. Pass customerData and Billwave will create the customer the first time it sees the customer id. On later calls customerData only backfills what is missing (a name if the customer has none) — it never overwrites existing values; use billwave.customer() to update a customer.

await billwave.track({
  customer: "workspace_alpha",          // becomes the customer's externalId
  feature: "agent_turns",
  customerData: {
    email: "owner@acme.com",            // required — used for receipts/checkout
    name: "Acme (Alpha workspace)",     // optional
    metadata: { plan_hint: "team" },    // optional, free-form
  },
});
FieldTypeRequiredNotes
emailstring✅Currently required for every customer; if your billing entity is a workspace, pass the owner's or a billing address
namestring-Display name
metadataobject-Stored on the customer

Without customerData, tracking an unknown customer returns code: "customer_not_found" (HTTP 404) and records nothing — unless the customer value itself is an email address, in which case the customer is created from it. If a newly created customer matches a plan with autoEnable: true, that plan is attached immediately so the very first track can succeed.

The same customer id then works for reads: billwave.customer.get("workspace_alpha"), usageHistory, and GET /customers/{id}.

When using entity, the entity must be created with addEntity() first. Tracking a non-existent entity returns an entity_not_found error.

Response

A successful track returns the full post-increment entitlement state, so you rarely need a follow-up check:

interface TrackResult {
  success: boolean;
  allowed: boolean;               // Whether the usage was accepted
  code: string;                   // tracked, tracked_overage, addon_credits_used, limit_exceeded, …
  environment: "sandbox" | "live"; // Where this was recorded (also X-Billwave-Environment header)
  unlimited: boolean;             // true when no finite cap applies (limit === null)
  usage: number | null;           // Usage this period, after this call
  limit: number | null;           // Plan limit; null = unlimited
  balance: number | null;         // limit - usage; null = unlimited
  resetsAt: string | null;        // ISO timestamp of the next reset
  resetInterval: string | null;   // "monthly", "daily", …
  credits:
    | {
        source: "credit_system";
        systemSlug: string;
        costPerUnit: number;
        bonusBalance: number;
        addonBalance: number;
        totalBalance: number | null;
        plan: { used: number; limit: number | null; balance: number | null; resetsAt: string };
      }
    | {
        source: "prepaid";
        bonusBalance: number;
        addonBalance: null;
        totalBalance: number | null;
        plan: { used: number; limit: number | null; balance: number | null; resetsAt: string };
      }
    | null;
  details: {
    message: string;              // Human-readable explanation
    plan?: string;                // Slug of the plan granting access (as in your catalog)
    planName?: string;
    trial?: boolean;
    trialEndsAt?: string | null;
    creditSystem?: string;
    overage?: { … };              // Present when this usage went into billable overage
    pricing?: { … };              // Present for chargeable metered features
  };
}
{
  "success": true,
  "allowed": true,
  "code": "tracked",
  "environment": "sandbox",
  "unlimited": false,
  "usage": 3,
  "limit": 100,
  "balance": 97,
  "resetsAt": "2026-10-01T00:00:00.000Z",
  "resetInterval": "monthly",
  "credits": null,
  "details": { "message": "Tracked 3 agent_turns on Pro.", "plan": "pro", "planName": "Pro" }
}

Result codes

codeallowedMeaning
trackedtrueRecorded within the plan limit
tracked_overagetrueRecorded beyond the limit; billed as overage
addon_credits_usedtruePlan credits exhausted; add-on credits covered it
bonus_credits_usedtrueCovered by a manual bonus balance
limit_exceededfalseLimit reached and overage is blocked — nothing recorded
insufficient_creditsfalseNot enough credits — nothing recorded
customer_not_foundfalseUnknown customer and no customerData (HTTP 404)
feature_not_foundfalseUnknown feature slug
no_active_subscriptionfalseCustomer has no active plan
feature_not_in_planfalseThe active plan doesn't include this feature
entity_not_foundfalseentity given but not created via addEntity()

Errors that are not entitlement results (invalid body, bad key, wrong environment) come back as { success: false, error: { code, message }, environment } with HTTP 400/401.

Examples

Basic Usage Tracking

import { Billwave } from "@digvijay-x1/billwave";

const billwave = new Billwave({ secretKey: process.env.BILLWAVE_API_KEY });

// Track a single API call
await billwave.track({
  customer: "user_123",
  feature: "api_calls",
});

Track Custom Amounts

// Track storage usage (e.g., 1.5 MB uploaded)
await billwave.track({
  customer: "user_123",
  feature: "storage_mb",
  value: 1.5,
});

// Track multiple items at once
await billwave.track({
  customer: "user_123",
  feature: "messages_sent",
  value: 10,
});

With Metadata

await billwave.track({
  customer: "user_123",
  feature: "api_calls",
  value: 1,
  metadata: {
    endpoint: "/api/generate",
    responseTime: 234,
    model: "gpt-4",
  },
});

Check Then Track Pattern

// Common pattern: check quota, then track usage
async function processRequest(userId: string) {
  // 1. Check if customer has remaining quota
  const check = await billwave.check({
    customer: userId,
    feature: "api_calls",
  });

  if (!check.allowed) {
    throw new Error(`Quota exceeded. Resets at ${check.resetsAt}`);
  }

  // 2. Process the request
  const result = await expensiveOperation();

  // 3. Track usage after successful completion
  await billwave.track({
    customer: userId,
    feature: "api_calls",
  });

  return result;
}

Billing Models

Pay-per-use

Track every unit and invoice at the end of the billing period:

// Track each AI token generated
await billwave.track({
  customer: "user_123",
  feature: "ai_tokens",
  value: response.usage.total_tokens,
});

Monthly Quotas

Track against a fixed monthly limit:

const result = await billwave.track({
  customer: "user_123",
  feature: "exports",
});

console.log(`${result.balance} exports remaining this month`);

Credit-based

Consume from a credit-backed feature:

const result = await billwave.track({
  customer: "user_123",
  feature: "gpt-4",
  value: 1,
});

if (result.credits?.source === "credit_system") {
  console.log("Plan credits left:", result.credits.plan.balance);
  console.log("Add-on credits left:", result.credits.addonBalance);
}

Best Practices

  1. Track after success - Only track after the operation completes successfully
  2. Non-blocking - Use fire-and-forget for non-critical tracking
  3. Batch if needed - For high-volume, batch multiple events together
  4. Include context - Add metadata for debugging and analytics

On this page

Ask about billwave

Ready

Start a new chat below.

Powered by Cull