VellumCharter is the layer between “they paid” and “they can use it.” Your app calls one fast endpoint to check what a customer is allowed to do; the rest of the
API runs checkout, subscriptions, billing, provisioning, and prepaid credits. Every endpoint
below has a JavaScript, Python, and raw-HTTP example — pick a language once and it applies to
the whole page.
Base URL https://api.vellumcharter.com
These are server-side APIs. Your API key carries a secret — keep it on your
backend, never ship it to a browser or mobile client.
Examples in
Authentication
Pass your key in the x-api-key header on every request. Keys look like vlm_<tenantId>.<secret> and are created in your dashboard. A key is
scoped to one tenant — requests for any other tenant’s path get a 403.
Python also ships an AsyncVellumCharterClient with the same methods.
import { VellumCharterClient } from '@vellumcharter/sdk';
const vellum = new VellumCharterClient({
apiKey: process.env.VELLUM_API_KEY!, // vlm_<tenantId>.<secret>
tenantId: 'acme',
// baseUrl defaults to https://api.vellumcharter.com
// cacheTtlMs defaults to 30000 (entitlement reads); 0 disables caching
});
Conventions
Requests and responses are JSON. Path parameters (tenantId, accountId, customerId, …) are your own identifiers. Errors come back
as { "error": "..." } with a matching status code.
StatusMeaning
400Invalid request — bad or missing parameters, malformed JSON.
403Bad API key, or the key’s tenant doesn’t match the path.
404Not found. For entitlements this just means “no access”.
409Conflict — e.g. insufficient credits on a consume. Nothing changed.
500Server error.
A few “not an error” cases worth knowing: a customer with no entitlements returns 404 (the SDKs hand you an empty, no-access set — no null check needed); a
never-purchased credit balance reads as 0, not 404; and a repeated
credit consume with the same idempotencyKey returns 200 with duplicate: true rather than deducting twice.
Entitlements
The hot path. One fast, cached lookup answers “what can this customer do right now?” — already resolved from what they pay for. This is the call your app makes on (almost) every request.
Returns the pre-flattened entitlement blob: which modules are unlocked and the plan config. A customer with no record returns 404 — treat that as “no access”. The SDKs turn it into an empty set so you never null-check.
// One call, instant yes/no — cached ~30s by default.
if (await vellum.can('cust_123', 'exports')) {
// ...let them export
}
// Or read the whole resolved set:
const ent = await vellum.getEntitlements('cust_123');
ent.isActive(); // true while active/trialing
ent.has('reports'); // module check
ent.config('max_users'); // a plan config value
// After a webhook tells you it changed, drop the cache:
vellum.invalidate('cust_123');
{ "tenantId": "acme", "customerId": "cust_123", "error": "no entitlements" }
Checkout & subscriptions
Turn a customer into a paying one and manage their subscription. Hosted checkout returns a URL you redirect to; the server-side variants act directly when the account already has a saved card.
Creates a hosted subscription-checkout session. Redirect the browser to the returned URL; the customer enters payment there. Their entitlements are resolved automatically once the payment completes.
customerIdrequiredThe member who gets the seat.
planId / planVersionrequiredThe specific plan version to subscribe to.
seatsNumber of seats (default 1).
successUrl / cancelUrlrequiredWhere the customer is returned after checkout.
Creates the subscription server-side, with no hosted page. The account must already have a saved payment method (see Billing or setup checkout). Honors the plan’s trial period if configured.
planId / planVersionrequiredThe specific plan version.
Creates a setup-mode hosted session to save or update a card without charging it. Redirect to the returned URL — useful before a server-side subscribe, or to fix an expired card.
successUrl / cancelUrlrequiredWhere the customer is returned after checkout.
const { url } = await vellum.createSetupCheckout({
accountId: 'acme',
successUrl: 'https://app.example.com/billing/ok',
cancelUrl: 'https://app.example.com/billing/cancel',
});
// redirect to `url` to capture a card (no charge)
Cancels immediately, or at the end of the current period with ?atPeriodEnd=true (the customer keeps access until then).
atPeriodEndQuery param; true to cancel at period end (default false).
await vellum.cancelSubscription('acme', 'cust_123', true); // at period end
Response
{ "canceled": "cust_123", "atPeriodEnd": true }
Billing
Read-and-manage helpers over the account’s cards and invoices — enough to build a self-serve billing page. The underlying payment platform still owns invoicing, proration, tax, and dunning; these are a thin, camelCase view.
Register the accounts (orgs) and customers (members) your billing hangs off, and update their lifecycle status. Create the account first, then its customers, then subscribe.
POST/tenants/{tenantId}/accounts
Create an account
Creates a billing account. Optionally create a new billing customer for it (createBillingCustomer) or adopt an existing one (billingCustomerId).
accountIdrequiredYour identifier for the org.
name / emailOptional billing contact details.
createBillingCustomerCreate a new billing customer (default false).
billingCustomerIdAdopt an existing billing customer instead.
Prepaid, pay-as-you-go balances that sit alongside subscriptions. A customer buys a pack with a one-time payment, and you consume credits as a feature is used. Consumption is atomic, all-or-nothing, and idempotent.
Creates a one-time (not recurring) hosted Checkout session to buy a prepaid pack. Redirect to the URL; the credits land in the customer’s wallet once payment completes. Buying quantity N grants the pack amount × N.
customerIdrequiredWhose wallet receives the credits.
creditTypeIdrequiredWhich credit type to buy.
packIdrequiredThe pack SKU.
quantityNumber of packs (default 1).
successUrl / cancelUrlrequiredWhere the customer is returned after checkout.
Atomically deducts credits as a feature is used. Pass a stable idempotencyKey (e.g. the job id) so a retry is a safe no-op — it returns the already-applied balance with duplicate: true. If the balance is insufficient, nothing is deducted and you get a 409.
amountrequiredCredits to deduct (integer ≥ 1).
idempotencyKeyrequiredStable key per logical operation.
try {
const r = await vellum.consumeCredits('cust_123', {
creditTypeId: 'render_minutes',
amount: 30,
idempotencyKey: 'render:job_42',
});
// r.balance, r.duplicate
} catch (e) {
if (e instanceof VellumCharterApiError && e.status === 409) {
// insufficient balance — nothing was deducted
}
}