Billwave
SDK Reference

check()

Check customer entitlements and feature access

check()

Check if a customer has access to a specific feature or entitlement. Use this for access control and gating.

Signature

await billwave.check({
  customer: string,    // Your internal user ID
  feature: string,     // Feature key to check
  value?: number,      // Units to check against limit (default: 1)
  entity?: string,     // Optional entity scope (seat, workspace, org)
  sendEvent?: boolean, // Atomically track if allowed
  customerData?: {     // Optional: auto-create customer
    email: string,
    name?: string,
    metadata?: Record<string, unknown>,
  },
}): Promise<CheckResult>

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 check access for
valuenumber-Units the caller intends to consume. Default 1.
entitystring-Scope to an entity (seat, workspace…). Must already exist.
sendEventboolean-If true and access is allowed, record value atomically (check + track in one round trip). Default false.
customerDataobject-Create the customer on first use — same shape and semantics as in track()

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

Response

interface CheckResult {
  allowed: boolean;
  code: string;                    // access_granted, overage_allowed, limit_exceeded, …
  environment: "sandbox" | "live"; // Where the check ran (also X-Billwave-Environment header)
  unlimited: boolean;              // true when access is granted with no finite cap
  usage: number | null;            // null for boolean features
  limit: number | null;            // null = unlimited (see `unlimited`)
  balance: number | null;          // limit - usage; null = unlimited
  resetsAt: string | null;
  resetInterval: string | null;
  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;
    plan?: string;                 // Slug of the plan granting access
    planName?: string;
    trial?: boolean;
    trialEndsAt?: string | null;
    creditSystem?: string;
    pricing?: { … };               // For chargeable metered features
    paymentStatus?: "past_due";    // Renewal failed; provider is retrying (see Dunning)
    graceEndsAt?: string;          // Backstop date if the provider never resolves it
  };
}

unlimited is the explicit form of "limit is null and access was granted". For credit-backed features it reflects credits.totalBalance === null. For pay-per-use features it is true (there is no cap) — look at details.pricing to see what usage will cost.

Result codes

codeallowedMeaning
access_grantedtrueWithin limits (or boolean feature enabled)
overage_allowedtrueOver the limit, but the plan allows billable overage
addon_credits_usedtruePlan credits exhausted; add-on credits would cover it
limit_exceededfalseLimit reached and overage is blocked
insufficient_creditsfalseNot enough credits for value
customer_not_foundfalseUnknown customer and no customerData
feature_not_foundfalseUnknown feature slug
no_active_subscriptionfalseCustomer has no active plan
feature_not_in_planfalseThe active plan doesn't include this feature

Dunning

When a renewal charge fails, the provider marks the subscription past_due and retries the card over the following days, emailing the customer. Billwave follows the provider: the plan's entitlements keep flowing while the subscription is past_due, and access is revoked when the provider ends it (canceled / unpaid) — or reactivated when a retry succeeds. During dunning check() and track() still return allowed: true, and details tells you why:

const access = await billwave.check({ customer: "user_123", feature: "reports" });

if (access.allowed && access.details.paymentStatus === "past_due") {
  // Still allowed — show "update your payment method", not "upgrade"
  showBillingBanner();
}

details.graceEndsAt is a backstop (45 days after the first failure) for the rare case where the provider never sends a terminal event; in practice the provider resolves the subscription long before then.

Examples

Basic Access Check

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

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

// Check if user can access premium features
const result = await billwave.check({
  customer: "user_123",
  feature: "premium_features",
});

if (result.allowed) {
  // Grant access
  showPremiumContent();
} else {
  // Prompt upgrade
  showUpgradeModal();
}

Metered Features

// Check API call quota
const result = await billwave.check({
  customer: "user_123",
  feature: "api_calls",
});

if (!result.allowed) {
  console.log("Quota exceeded", result.balance, result.resetsAt);
}

// Process the request...

Credit-backed Features

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

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

For credit-backed features, credits is the canonical balance object. The top-level balance remains the generic remaining balance for the checked feature.

Multiple Feature Checks

// Check multiple features in parallel
const [canExport, canAnalyze, canShare] = await Promise.all([
  billwave.check({ customer: "user_123", feature: "export" }),
  billwave.check({ customer: "user_123", feature: "analytics" }),
  billwave.check({ customer: "user_123", feature: "team_sharing" }),
]);

console.log({
  export: canExport.allowed,
  analytics: canAnalyze.allowed,
  sharing: canShare.allowed,
});

Best Practices

  1. Cache results - For high-traffic endpoints, cache check results for a few seconds
  2. Fail open or closed - Decide your failure mode if the API is unreachable
  3. Check early - Validate access at the start of expensive operations
  4. Use middleware - Create reusable middleware for common access patterns

On this page

Ask about billwave

Ready

Start a new chat below.

Powered by Cull