Billwave
SDK Reference

customer()

Create, resolve, look up, list, and configure customers

customer()

Use billwave.customer() to create or resolve a customer by email or ID. The response includes the customer's current billing configuration, and the same namespace exposes helpers for customer-specific billing controls.

For the dedicated guide covering customer-level billing overrides, precedence, and recommended usage, see Customer Config.

For customer-facing usage charts and usage dashboards, the same namespace also exposes usageHistory().

Every read on this namespace (get(), usageHistory(), and the setters) accepts the same identifier you use in track() / check(): your own external customer ID, the customer's email, or the internal Billwave ID.

Signature

const customer = await billwave.customer({
  id?: string,         // Optional: Your customer ID
  email: string,       // Required: Customer email
  name?: string,       // Optional: Display name
  metadata?: Record<string, unknown>, // Optional: Custom data
});

const customer = await billwave.customer.get(customer: string);

const page = await billwave.customer.list({
  limit?: number,      // 1-100, default 50
  offset?: number,     // default 0
  search?: string,     // substring match on email, name, externalId
  email?: string,      // exact (case-insensitive)
  externalId?: string, // exact
});

await billwave.customer.setFeatureConfig({
  customer: string,
  feature: string,
  overage?: "block" | "charge" | null,
  maxOverageUnits?: number | null,
});

await billwave.customer.setOverageLimit({
  customer: string,
  maxOverageAmount: number | null,
  onLimitReached?: "block" | "notify",
});

await billwave.customer.usageHistory({
  customer: string,
  range?: "7d" | "30d" | "90d" | "custom",
  granularity?: "day" | "week" | "month",
  feature?: string | null,
  groupBy?: "total" | "feature",
  timezone?: string,
  from?: string,
  to?: string,
});

Parameters

billwave.customer(params)

ParameterTypeRequiredDescription
idstring-Your internal customer ID
emailstring✅Customer email address
namestring-Customer display name
metadataobject-Custom key-value data

billwave.customer.get(customer)

ParameterTypeRequiredDescription
customerstring✅External customer ID (what you pass to track), email, or internal ID

Returns the same CustomerResult as billwave.customer(). Throws BillwaveError with Customer not found when nothing matches within your organization.

billwave.customer.list(params?)

ParameterTypeRequiredDescription
limitnumber-Page size, 1–100. Defaults to 50.
offsetnumber-Customers to skip. Defaults to 0.
searchstring-Case-insensitive substring match on email, name and external ID
emailstring-Exact email match (case-insensitive)
externalIdstring-Exact external ID match

Results are newest first. total is the number of customers matching the filters, so offset + data.length < total means there is another page.

billwave.customer.setFeatureConfig(params)

ParameterTypeRequiredDescription
customerstring✅Customer ID or identifier
featurestring✅Feature slug or ID
overage"block" | "charge" | null-Override overage behavior for this feature
maxOverageUnitsnumber | null-Override the hard overage cap for this feature

Provide at least one of overage or maxOverageUnits. Pass null to clear an existing override.

billwave.customer.setOverageLimit(params)

ParameterTypeRequiredDescription
customerstring✅Customer ID or identifier
maxOverageAmountnumber | null✅Customer-wide spend cap in minor units
onLimitReached"block" | "notify"-Behavior when the spend cap is reached. Defaults to block

Pass null for maxOverageAmount to remove the spend cap.

billwave.customer.usageHistory(params)

ParameterTypeRequiredDescription
customerstring✅Customer ID
range"7d" | "30d" | "90d" | "custom"-Preset range or custom window. Defaults to 30d.
granularity"day" | "week" | "month"-Bucket size for aggregation. Defaults to day.
featurestring | null-Optional feature slug or ID to filter to one feature.
groupBy"total" | "feature"-Return only the total series or include per-feature breakdowns. Defaults to total.
timezonestring-IANA timezone used for bucket boundaries. Defaults to UTC.
fromstring-Inclusive YYYY-MM-DD start date when range is custom.
tostring-Inclusive YYYY-MM-DD end date when range is custom.

Use usageHistory() when you want chart-friendly usage buckets.

Use billwave.billing.usage() when you want uninvoiced overage and estimated billable amounts.

Returns

billwave.customer(), get(), setFeatureConfig(), and setOverageLimit() all return the customer plus the current customer billing config.

interface CustomerResult {
  id: string;                 // Internal Billwave ID
  externalId: string | null;  // Your ID (the `customer` value you track with)
  email: string;
  name?: string | null;
  metadata?: Record<string, unknown> | null;
  billing: {
    overageLimit: {
      maxOverageAmount: number | null;
      onLimitReached: "block" | "notify";
      createdAt: number;
      updatedAt: number;
    } | null;
    featureConfigs: Array<{
      feature: {
        id: string;
        slug: string | null;
        name: string;
      };
      overage: "block" | "charge" | null;
      maxOverageUnits: number | null;
      createdAt: number;
      updatedAt: number;
    }>;
  };
  createdAt: number;
  updatedAt: number;
}

billwave.customer.list() returns a page of lightweight customer summaries (no billing config):

interface CustomerListResult {
  success: true;
  data: Array<{
    id: string;
    externalId: string | null;
    email: string;
    name: string | null;
    metadata: Record<string, unknown> | null;
    createdAt: number;
    updatedAt: number;
  }>;
  total: number;
  limit: number;
  offset: number;
}

billwave.customer.usageHistory() returns aggregated usage history:

interface UsageHistoryResult {
  customer: {
    id: string;
  };
  query: {
    range: {
      from: string;
      to: string;
    };
    granularity: "day" | "week" | "month";
    feature: string | null;
    groupBy: "total" | "feature";
    timezone: string;
  };
  totals: {
    usage: number;
    records: number;
  };
  series: Array<{
    bucket: string;
    value: number;
  }>;
  breakdown: Array<{
    feature: {
      id: string;
      slug: string | null;
      name: string;
      unit: string | null;
    };
    totals: {
      usage: number;
      records: number;
    };
    series: Array<{
      bucket: string;
      value: number;
    }>;
  }>;
}

Examples

Create or Resolve a Customer

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

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

// Create an org customer
const org = await billwave.customer({
  email: "billing@acme.com",
  name: "Acme Corporation",
  metadata: { industry: "SaaS" },
});

console.log(org.id);
console.log(org.billing.overageLimit); // null by default
console.log(org.billing.featureConfigs); // []

Resolve an Existing Customer

// Resolve existing customer by email
const existing = await billwave.customer({
  email: "billing@acme.com",
});

// Or by your internal ID
const byId = await billwave.customer({
  id: "org_acme",
  email: "billing@acme.com",
});

Look Up a Customer by Your Own ID

// The id you pass to track()/check() works for reads too
await billwave.track({ customer: "workspace_alpha", feature: "agent_turns" });

const customer = await billwave.customer.get("workspace_alpha");
console.log(customer.id);         // internal UUID
console.log(customer.externalId); // "workspace_alpha"

// Email works as well
const byEmail = await billwave.customer.get("billing@acme.com");

List and Search Customers

// First page, newest first
const page = await billwave.customer.list({ limit: 25 });

// Search across email, name and external id
const matches = await billwave.customer.list({ search: "acme" });

// Exact lookups
const { data } = await billwave.customer.list({ externalId: "workspace_alpha" });

// Walk every page
for (let offset = 0; ; offset += 100) {
  const { data, total } = await billwave.customer.list({ limit: 100, offset });
  for (const customer of data) console.log(customer.externalId ?? customer.email);
  if (offset + data.length >= total) break;
}

Set a Per-Feature Overage Policy

const updated = await billwave.customer.setFeatureConfig({
  customer: "cust_123",
  feature: "api_calls",
  overage: "block",
  maxOverageUnits: 1000,
});

console.log(updated.billing.featureConfigs);

Set a Customer-Wide Overage Spend Cap

const updated = await billwave.customer.setOverageLimit({
  customer: "cust_123",
  maxOverageAmount: 500_000,
  onLimitReached: "block",
});

console.log(updated.billing.overageLimit);

Clear Customer Billing Overrides

await billwave.customer.setFeatureConfig({
  customer: "cust_123",
  feature: "api_calls",
  overage: null,
  maxOverageUnits: null,
});

await billwave.customer.setOverageLimit({
  customer: "cust_123",
  maxOverageAmount: null,
  onLimitReached: "block",
});

Get Aggregate Usage History

const history = await billwave.customer.usageHistory({
  customer: "cust_123",
  range: "30d",
  granularity: "day",
  groupBy: "total",
  timezone: "Africa/Lagos",
});

console.log(history.totals.usage);
console.log(history.series);

Get Per-Feature Usage History

const history = await billwave.customer.usageHistory({
  customer: "cust_123",
  range: "90d",
  granularity: "week",
  groupBy: "feature",
});

for (const feature of history.breakdown) {
  console.log(feature.feature.slug, feature.totals.usage);
}

Use a Custom Date Window

const history = await billwave.customer.usageHistory({
  customer: "cust_123",
  range: "custom",
  granularity: "day",
  from: "2026-04-01",
  to: "2026-04-30",
});

Using the Customer Object

The returned customer object has convenience methods:

const org = await billwave.customer({ email: "org@acme.com" });

// Attach subscription
await org.attach({ product: "team" });

// Manage entities (seats)
await org.addEntity({
  feature: "seats",
  entity: "user_123",
  name: "John Doe",
});

const { entities } = await org.listEntities({ feature: "seats" });

Entity Management Methods

Customer objects provide these entity management methods:

customer.addEntity(params)

Add an entity (e.g., seat) to the customer.

await org.addEntity({
  feature: "seats",
  entity: "user_123",
  name: "John Doe",
  email: "john@acme.com",
  metadata: { role: "admin" },
});

Parameters:

  • feature (string, required): Feature slug
  • entity (string, required): Your entity ID
  • name (string, optional): Display name
  • email (string, optional): Contact email
  • metadata (object, optional): Custom data

customer.removeEntity(params)

Remove an entity and free up the slot.

await org.removeEntity({
  feature: "seats",
  entity: "user_123",
});

customer.listEntities(params?)

List entities for this customer.

// List all entities
const { entities } = await org.listEntities();

// Filter by feature
const { entities: seats } = await org.listEntities({ feature: "seats" });

Best Practices

  1. Create early - Create the customer before attaching subscriptions or adding entities
  2. Use consistent IDs - Either always pass your own id or always use email as identifier
  3. Store metadata - Use metadata for customer segmentation and analytics
  4. Use spend caps as safety rails - setOverageLimit() protects the whole customer across billable overage
  5. Use feature config for exceptions - setFeatureConfig() is best for one risky or contract-specific feature
  6. Use usageHistory for charts - usageHistory() is for usage dashboards, not invoice estimation
  7. Update on changes - Call customer() when user data changes to keep billing records in sync
  • Customer Config - Customer-level billing overrides and spend caps
  • billing - Uninvoiced overage, invoices, and invoice generation
  • attach() - Subscribe customer to a plan
  • Overage - How plan overage and customer overrides interact
  • addEntity() - Add entities/seats (flat API)
  • Seat Pricing Guide - Complete seat-based billing walkthrough

On this page

Ask about billwave

Ready

Start a new chat below.

Powered by Cull