Skip to content

Node.js SDK

The Node.js SDK provides typed resources, response validation, cancellation, timeouts, and structured errors. It is deliberately thin: the HTTP API remains the canonical contract.

import { Strent } from "@strent-dev/node";
export const strent = new Strent({
apiKey: process.env.STRENT_API_KEY!,
baseUrl: process.env.STRENT_BASE_URL,
timeoutMs: 1_000,
});
Option Default Purpose
apiKey Required Project API key used as a Bearer token
baseUrl https://api.strent.dev Override for local or self-managed API access
timeoutMs 1000 Default positive integer timeout per request
fetch globalThis.fetch Optional compatible fetch implementation
entitlements None What each Stripe product grants; see Check entitlements
cache true Enable positive reuse and availability fallback
onFallback Server warning Replace the warning with a synchronous event handler

Create one client per API key and base URL in your server process and reuse it. Never instantiate it in browser code.

Every method takes a params object with the Stripe customer ID.

Method Result HTTP operation
activeItems.list({ customer }) Active Stripe subscription items POST /v1/active_items/list
customers.hasActiveSubscription({ customer }) boolean: at least one active item Derived from active_items/list
activeItems.hasProduct({ customer, product }) boolean product membership Derived from active_items/list
entitlements.retrieve({ customer }) Every declared entitlement and its value Derived from active_items/list
entitlements.has({ customer, entitlement }) boolean entitlement value Derived from active_items/list
customers.retrieveBillingState({ customer }) Detailed subscription state POST /v1/customers/retrieve_billing_state
customers.refresh({ customer }) void after the refresh succeeds POST /v1/customers/refresh

The entitlement methods require the entitlements option and accept a null or undefined customer, answering from your defaults without a request.

customers.hasActiveSubscription is true when the customer has at least one active item. Stripe never leaves a subscription without items, so that is the same as having an access-bearing subscription.

The package exports ActiveItem, BillingState, BillingSubscription, BillingSubscriptionItem, BillingPrice, Entitlements, EntitlementsConfig, and EntitlementValue types for application code.

Each derived check reuses a recent saved list that contains the requested value, but a value missing from that list always costs a request. Chaining hasProduct calls across several plans therefore sends one request per absent product, which is every request for a free customer. When one request checks more than one product, read the list once and resolve in memory; for several entitlements, call entitlements.retrieve once:

const { data: items } = await strent.activeItems.list({ customer });
const productIds = new Set(items.map((item) => item.price.product.id));
const plan = productIds.has("prod_enterprise")
? "enterprise"
: productIds.has("prod_pro")
? "pro"
: "free";

Refresh new customers and Checkout returns

Section titled “Refresh new customers and Checkout returns”
await strent.customers.refresh({ customer: "cus_123" });
const hasSubscription = await strent.customers.hasActiveSubscription({
customer: "cus_123",
});

Call refresh when a user returns from Checkout or the Customer Portal. Also call it right after creating a Stripe customer if your app may check access before Strent processes Stripe’s customer.created webhook, a few seconds later, so that check returns false instead of CUSTOMER_STATE_NOT_READY.

refresh returns void after a successful refresh whose Stripe read starts after the call arrives. Its SDK timeout defaults to 15 seconds independently of the client read timeout; override it per call when needed. Strent waits up to 10 seconds and may reuse queued work. CUSTOMER_REFRESH_NOT_FINISHED means the wait expired while durable work continues. After success, this client clears all cached facts for that customer, including older reads still in flight. A failed refresh keeps the saved facts.

const hasPro = await strent.activeItems.hasProduct({
customer: "cus_123",
product: "prod_pro",
});

This returns true when an active item has that product, with any price, and false when no item matches. It uses POST /v1/active_items/list and shares the saved list with activeItems.list and other product checks. Your application decides what access that fact permits. Product IDs must be non-empty strings; custom IDs without a prod_ prefix are valid. Invalid customers or products throw StrentError before a request.

Every method accepts request options as its last argument:

const controller = new AbortController();
const result = await strent.customers.hasActiveSubscription(
{ customer: "cus_123" },
{ timeoutMs: 500, signal: controller.signal },
);

Request options apply when the SDK sends a request. Caller cancellation produces StrentAbortError. An elapsed SDK timeout produces StrentTimeoutError unless an eligible saved answer supplies fallback.

The cache is enabled by default and belongs to this client. It works equally with test and live keys.

Every check and entitlement result derives from one saved active-item list per customer, shared with activeItems.list. A boolean check returns true without a request when that list is less than 5 minutes old and yields true. An absent value, a missing list, or a list aged 5 minutes or more triggers a request, and each successful request replaces the shared list. activeItems.list and entitlements.retrieve always go to Strent, so a saved list cannot hide an upgrade. Successful refresh clears the saved list in this client. With cache: false, every check goes to Strent.

If a request fails with a timeout, network error, HTTP 5xx, invalid successful response, or HTTP 429, a check returns true only when its saved list is less than 1 hour old and yields true, and activeItems.list and entitlements.retrieve answer from the saved list under the same age limit. The fallback warning names the operation you called. Empty saved lists are valid fallback; a check’s false is never served from cache. HTTP 400, 401, 403, 409, other non-429 4xx, and caller abort never use fallback. Without an eligible saved answer, the original error is thrown.

customers.retrieveBillingState and customers.refresh never use cached responses. The cache holds up to 10,000 customers and evicts the least recently used. Both windows are fixed, measured from the request start with a monotonic clock. The SDK does not coalesce or automatically retry requests.

For immediate revocation, turn off both reuse and fallback with cache: false. For different policies in different paths, create separate clients; there is no per-call switch.

import { Strent } from "@strent-dev/node";
export const strent = new Strent({
apiKey: process.env.STRENT_API_KEY!,
cache: false,
});

Every fallback writes a server warning with the customer, operation, age in milliseconds, and original error. Positive reuse is silent. Replace the warning with a synchronous onFallback handler if you use your own logger:

import { Strent, type StrentFallbackEvent } from "@strent-dev/node";
export const strent = new Strent({
apiKey: process.env.STRENT_API_KEY!,
onFallback(event: StrentFallbackEvent) {
console.warn("Using saved Strent facts", event);
},
});

The event fields are operation, customer, ageMs, and the original error. A throwing handler is ignored so fallback still succeeds. With cache: false, the handler never runs.

Handle SDK errors →