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
| Parameter | Type | Required | Description |
|---|---|---|---|
customer | string | ✅ | Your identifier for the customer (e.g. your user or workspace ID). Also accepts the customer's email or internal ID. |
feature | string | ✅ | Feature slug (or ID) to check access for |
value | number | - | Units the caller intends to consume. Default 1. |
entity | string | - | Scope to an entity (seat, workspace…). Must already exist. |
sendEvent | boolean | - | If true and access is allowed, record value atomically (check + track in one round trip). Default false. |
customerData | object | - | 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
code | allowed | Meaning |
|---|---|---|
access_granted | true | Within limits (or boolean feature enabled) |
overage_allowed | true | Over the limit, but the plan allows billable overage |
addon_credits_used | true | Plan credits exhausted; add-on credits would cover it |
limit_exceeded | false | Limit reached and overage is blocked |
insufficient_credits | false | Not enough credits for value |
customer_not_found | false | Unknown customer and no customerData |
feature_not_found | false | Unknown feature slug |
no_active_subscription | false | Customer has no active plan |
feature_not_in_plan | false | The 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
- Cache results - For high-traffic endpoints, cache check results for a few seconds
- Fail open or closed - Decide your failure mode if the API is unreachable
- Check early - Validate access at the start of expensive operations
- Use middleware - Create reusable middleware for common access patterns