SDK reference — @bc-subscriptions/subscriber-sdk¶
Generated from the canonical integration guide
This page is generated from docs/integration/sdk-reference.md
— the single source of truth shared with the public
marketing site. Edit the canonical file, then run
npm --prefix tools/integration-docs-derive run derive.
Package:
@bc-subscriptions/subscriber-sdkSource:packages/subscriber-sdk/API surface reference:packages/subscriber-sdk/README.md
The subscriber-sdk is a typed TypeScript client for the bc-subscriptions portal API. It's the base layer used by @bc-subscriptions/storefront-catalyst and is available directly for fully custom headless integrations.
The client surface is intentionally lean (established in ADR-0034 §step-2): four namespaces — subscriptions, plans, products, cart — over the subscriber portal API. The SDK does not wrap auth; you obtain a portal session token from the portal auth endpoints (below) and pass it to createClient.
Installation¶
Client construction¶
import { createClient } from '@bc-subscriptions/subscriber-sdk';
const client = createClient({
token: sessionToken, // portal session JWT (see "Authentication" below) — required
baseUrl: 'https://subs-api.bigcommerce-testing-7727.workers.dev/api/v1',
// apiVersion: '2026-06-01', // optional → sent as the Accept-Version header
// onDeprecation: (info) => { ... }, // optional → fires on a Deprecation response header
});
createClient(config) returns a typed client object exposing subscriptions, plans, products, and cart. All methods are async and return typed response objects or throw a typed SDKError.
SDKClientConfig:
| Field | Type | Notes |
|---|---|---|
token |
string |
Required. Sent as Authorization: Bearer <token> on every request. Use a portal session JWT; for public-only reads (plans, products) any valid token still satisfies the header. |
baseUrl |
string? |
API base including the version prefix (e.g. …/api/v1). |
apiVersion |
string? |
Sent as the Accept-Version request header when set. |
onDeprecation |
(info) => void |
Invoked with { route, sunset, replacement } when a response carries a Deprecation header. |
The returned client is immutable — there is no client.token setter. To swap tokens (e.g. after upgrading from an unauthenticated to an authenticated session), construct a new client with the new token.
Authentication¶
The SDK does not expose auth methods. Obtain a portal session token directly from the portal auth endpoints, then pass it to createClient:
// 1. Request a magic link (unauthenticated)
await fetch(`${API_BASE}/api/v1/portal/auth/request-link`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'shopper@example.com' }),
});
// → email sent with a short-lived link containing a one-time token
// 2. Verify the token from the link URL → portal session JWT
const res = await fetch(`${API_BASE}/api/v1/portal/auth/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: tokenFromLinkUrl }),
});
const { token } = await res.json();
// 3. Construct an authenticated client
const client = createClient({ token, baseUrl: `${API_BASE}/api/v1` });
Tokens are short-lived. Store in sessionStorage; do not persist to localStorage. (Catalyst storefronts can use the higher-level createApiClient from @bc-subscriptions/storefront-catalyst, which wraps requestMagicLink / verifyMagicLink — see the Catalyst guide.)
Subscriptions¶
All subscription methods require an authenticated client. The session token scopes results to the authenticated subscriber — there is no customer_id parameter.
client.subscriptions.list¶
const page = await client.subscriptions.list({
cursor: undefined, // optional — opaque cursor from a prior page's next_cursor
limit: 20, // optional
});
// Returns: { data: Subscription[]; next_cursor: string | null }
Pagination is cursor-based. Pass the prior page's next_cursor to fetch the next page; a null next_cursor means there are no more pages.
interface Subscription {
id: string;
status: string; // 'active' | 'paused' | 'cancelled' | 'past_due' | …
plan_id: string;
customer_id: string;
store_hash: string;
next_billing_date: string | null; // ISO 8601
created_at: string;
updated_at: string;
[key: string]: unknown; // additional plan/product snapshot fields
}
client.subscriptions.get¶
client.subscriptions.pause / resume¶
await client.subscriptions.pause('sub_abc123', { idempotencyKey: 'pause-sub_abc123-1' });
await client.subscriptions.resume('sub_abc123');
// Each returns: Subscription
client.subscriptions.cancel¶
await client.subscriptions.cancel(
'sub_abc123',
{ reason: 'too_expensive' }, // optional params
{ idempotencyKey: 'cancel-sub_abc123-1' }, // optional
);
// Returns: Subscription (status: 'cancelled')
client.subscriptions.skip / swap¶
// Skip the next scheduled charge
await client.subscriptions.skip('sub_abc123');
// Swap to a different product variant
await client.subscriptions.swap('sub_abc123', { newVariantId: '9876' });
// Each returns: Subscription
client.subscriptions.updatePaymentMethod¶
await client.subscriptions.updatePaymentMethod('sub_abc123', {
paymentMethodId: 'instr_abc', // BC vault instrument id
});
// Returns: Subscription
Payment methods reference BC vault instrument IDs — raw card data is never stored in bc-subscriptions (per ADR-0037).
client.subscriptions.performAction¶
Generalized action dispatch (US-17.5) for lifecycle actions not covered by a dedicated method:
await client.subscriptions.performAction('sub_abc123', 'reschedule', { next_billing_date: '2026-07-01' });
// Returns: Subscription
All mutating methods accept an optional trailing { idempotencyKey } — sent as the Idempotency-Key header so retries are safe.
Plans¶
Plans are public-read.
client.plans.list¶
const page = await client.plans.list({ cursor: undefined, limit: 20 });
// Returns: { data: Plan[]; next_cursor: string | null }
interface Plan {
id: string;
name: string;
store_hash: string;
status: string;
billing_frequency: string;
created_at: string;
updated_at: string;
[key: string]: unknown;
}
client.plans.get¶
Products¶
Discovery of products that have subscription plans attached.
client.products.listSubscribable¶
const page = await client.products.listSubscribable({ cursor: undefined, limit: 20 });
// Returns: { data: SubscribableProduct[]; next_cursor: string | null }
interface SubscribableProduct {
id: string;
name: string;
variant_id: string;
plans: string[]; // plan ids available for this product
[key: string]: unknown;
}
Cart¶
Attach a subscription intent to a BC cart before checkout.
client.cart.addSubscriptionItem¶
const result = await client.cart.addSubscriptionItem(
'cart_123',
{ plan_id: 'plan_abc', variant_id: '9876', quantity: 1 },
{ idempotencyKey: 'cart_123-plan_abc' }, // optional
);
// Returns:
// {
// cart_id: string;
// subscription_preview: { plan_id: string; first_billing_date: string; recurring_price: number };
// }
client.cart.removeSubscriptionItem¶
Error handling¶
The SDK throws a typed SDKError — a discriminated union keyed on type (not error classes). Catch and switch on err.type:
import { createClient } from '@bc-subscriptions/subscriber-sdk';
import type { SDKError } from '@bc-subscriptions/subscriber-sdk';
const client = createClient({ token, baseUrl });
try {
const sub = await client.subscriptions.get('sub_does_not_exist');
} catch (e) {
const err = e as SDKError;
switch (err.type) {
case 'not_found': // 404 — doesn't exist or not owned by this subscriber
console.error('Not found');
break;
case 'auth_failed': // 401 — token missing, expired, or invalid → re-auth
redirectToLogin();
break;
case 'forbidden': // 403
console.error('Forbidden');
break;
case 'conflict': // 409 — e.g. cancel an already-cancelled subscription
console.error('Action not valid for current state');
break;
case 'validation_failed': // 422 — err.fields lists the offending fields
console.error('Validation failed:', err.fields);
break;
case 'server_error': // 500
default:
throw e;
}
}
SDKError variants and their HTTP status:
type |
status | extra fields |
|---|---|---|
auth_failed |
401 | — |
forbidden |
403 | details? |
not_found |
404 | details? |
conflict |
409 | details? |
validation_failed |
422 | fields: string[] |
server_error |
500 | — |
The typed error model mirrors the API's domain error helpers at apps/api/src/errors/ (see project memory typed-error-pattern).
Retries¶
The SDK does not retry automatically. Implement retry in your application layer, and prefer passing an idempotencyKey to mutating calls so retries are safe:
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (e) {
const err = e as SDKError;
// Only retry transient server errors — never 4xx client errors.
if (attempt === maxAttempts || err.type !== 'server_error') throw e;
await new Promise((r) => setTimeout(r, 200 * attempt));
}
}
throw new Error('unreachable');
}
const sub = await withRetry(() => client.subscriptions.get('sub_abc123'));
Deprecation signalling¶
When the API marks a route deprecated, the response carries a Deprecation header (and Sunset / successor Link). Pass an onDeprecation callback to createClient to observe these:
const client = createClient({
token,
baseUrl,
onDeprecation: ({ route, sunset, replacement }) => {
console.warn(`[subs-sdk] ${route} is deprecated (sunset ${sunset}); use ${replacement}`);
},
});
Types¶
Method response interfaces (Subscription, Plan, SubscribableProduct, the *Page cursor wrappers, and the cart types) are exported from the package root:
import type {
Subscription, SubscriptionPage,
Plan, PlanPage,
SubscribableProduct, SubscribableProductPage,
SubscriptionCartItem, CartSubscriptionResult,
SDKClient, SDKClientConfig, SDKError,
} from '@bc-subscriptions/subscriber-sdk';
These are currently hand-authored structural interfaces. A generation pipeline against apps/api/openapi.yaml exists (npm run build:types → scripts/build-types.ts, using openapi-typescript); full openapi-driven type generation is tracked in Hive #580.
OpenAPI spec¶
The full machine-readable API spec is at apps/api/openapi.yaml. Swagger UI is served at https://subs-api.bigcommerce-testing-7727.workers.dev/docs.
Use the spec to: - Verify field shapes before writing client code - Generate mock handlers (e.g. with MSW) for test environments - Validate webhook payloads against the schema
Related¶
@bc-subscriptions/storefront-catalyst— React component layer + higher-levelcreateApiClient(wraps auth) on top of this SDK@bc-subscriptions/react— React hooks and context provider (released in lockstep with this SDK)- ADR-0034 — SDK substrate decision (the lean 4-namespace client)
- ADR-0037 — stored instrument vault (why raw card data is never in bc-subscriptions)
- Integration guide — choose your integration tier