Skip to main content

Customers

A customer is anything you meter: a user, an org, a team, a workspace, an API key, an agent. Each has an ID (yours), a plan, a type, and its state: meters, overrides, gives and takes, grants, and caps. The engine keeps customers in memory inside the policy document.

// An org on the team plan.
await limitr.ensureCustomer('acme', 'team', 'org', 'Acme Inc.');

// A user in it, on the org's plan.
await limitr.ensureCustomer('grace', '', 'user', 'Grace', ['acme']);

Creating customers​

CallDoes
ensureCustomer(id, plan?, type?, label?, refs?, alts?, metadata?)Creates the customer if it doesn't exist. Safe to call on every request.
createCustomer(id, plan?, type?, label?, refs?, alts?, metadata?)Creates the customer, or updates it if the ID exists. Returns the record.
setCustomer(record)Replaces a customer from a saved record (Ex. from your database).
ensureSetCustomer(record)Sets the record only if that customer isn't loaded.
loadCustomers(records)Loads many records: an array, or an object keyed by ID.
removeCustomer(id)Deletes the customer. Sends customer-removed.

Arguments after the ID are optional:

ArgumentDefaultNotes
plan'''' takes the plan from the refs, then the default plan. See Plans.
type'user'Any string: 'org', 'team', 'workspace', 'seat'. Used by scope and caps.
label'User'A name for people.
refsnoneIDs of related customers, Ex. a user's team and org.
altsnoneOther IDs that resolve to this customer (below).
metadatanoneAnything: an object or a JSON string. Rules can read it.

Creating a customer creates its meters and the grants for its plan's included topups, and sends customer-set.

IDs​

A customer ID is yours: a user ID, an org ID, an API key, any string without a period. Customer IDs and alt IDs can't contain a period (.), because the policy finds customers by . paths. An ID with one is refused with an error in the log: createCustomer returns undefined, ensureCustomer returns false, and the customer isn't created. To use an email, replace its periods with a placeholder of your own, or use your user ID and keep the email in the customer's metadata.

Refs​

Refs link customers: a user refs its team, a team refs its org. They're used for:

  • Plans. A customer with no plan of its own uses the first plan in its refs. Users can be on their org's plan.
  • Scope. An entitlement with scope: 'org' meters on the nearest org through the refs, so any user's seat invite counts against the org.
  • Shared caps. A cap with shared: true on an org applies to every customer whose refs lead to it.
  • Gives and takes. A customer on its refs' plan also gets that customer's gives and takes.

Read them with customerRefs(id). Change them with set_customer_refs(id, refs) (from TypeScript, limitr.docCall('policy.set_customer_refs', id, refs)), or by setting the customer's record.

Alt IDs​

Alt IDs let other identifiers resolve to the same customer: an auth provider's subject, a payment provider's customer ID. Every call that takes a customer ID accepts an alt ID.

await limitr.ensureCustomer('u_123', 'pro', 'user', 'Ada', null, ['auth0|8f3a', 'cus_Q1w2e3']);
await limitr.addAltID('u_123', 'github-4411');
await limitr.removeAltID('github-4411');

Alt IDs follow the same rule as IDs: no periods. addAltID returns false for one.

Overrides​

An override replaces an entitlement's limit for one customer: a bigger allowance for a big customer, a different mode, a trial with an end date. The plan doesn't change.

// Acme gets 5,000 requests instead of the plan's 100.
await limitr.createCustomerOverride('acme', 'api', 5000);

// Make it soft for two weeks. Unset fields copy the current limit, so the value stays 5,000.
const twoWeeks = Date.now() + 14 * 86_400_000;
await limitr.createCustomerOverride('acme', 'api', undefined, twoWeeks, undefined, 'soft');

await limitr.removeCustomerOverride('acme', 'api');

createCustomerOverride(id, entitlement, value?, expires_on?, credit?, mode?, increment?, resets?, reset_inc?, reset_sch?). In Stof the order is create_customer_override(id, entitlement, expires?, credit?, mode?, value?, ...), and named arguments work: create_customer_override('acme', 'api', value = 5000).

  • Unset fields copy the current limit (the plan's, or the existing override). A new override replaces the old one.
  • Expiry. An override past its expires_on is removed on the next read or call, and the plan's limit applies again.
  • A limit on a flag. An override can meter an entitlement that has no limit, given a credit.
  • Only on that customer. Users on an org's plan don't get the org's overrides. For one number shared by the org, meter the entitlement on the org (scope: 'org'): the org's override then applies to every user's calls.
  • Overrides change how much a customer gets. To change whether it has an entitlement at all, give or take it.

Gives and takes​

Give or take changes whether one customer has an entitlement, whatever its plan says. Use them for custom deals, keeping a feature through a downgrade, trials of a higher plan's feature, and pausing one feature.

const twoWeeks = Date.now() + 14 * 86_400_000;

// SSO, from the closest plan that has it.
await limitr.giveEntitlement('acme', 'sso');

// Enterprise's API limit, for 14 days.
await limitr.giveEntitlement('acme', 'api', 'enterprise', twoWeeks);

// Pause exports, then put them back to what the plan says.
await limitr.takeEntitlement('acme', 'exports');
await limitr.restoreEntitlement('acme', 'exports');
CallDoes
giveEntitlement(id, entitlement, plan?, expiresOn?)Gives the entitlement as plan defines it. Without plan, the closest plan that has it.
takeEntitlement(id, entitlement, expiresOn?)Denies it even though the plan (or a give) has it.
restoreEntitlement(id, entitlement)Undoes a give or a take. False if there was neither.
  • Nothing is invented. A give uses a plan's definition of the entitlement, limits included. Give returns false when no plan defines it, or the customer doesn't exist.
  • The closest plan is the customer's own plan, then its refs' plans (nearest first), then the policy's plans in the order they're written. closest_plan_with(customer, entitlement) in Stof says which one it picks.
  • Takes keep state. Meters and overrides stay, so a restore picks up where it left off.
  • Latest wins. A new give or take of an entitlement replaces the last one. Expired ones are ignored.
  • Stored on the customer in given ({ plan, expires_on } per entitlement) and taken ({ expires_on }), so they last through plan changes and are saved with the customer.
  • Inherited like plans. A user on its org's plan gets the org's gives and takes. Its own win.
  • Overrides still apply on top: give a feature, then change its limit for that customer.
  • enabled rules still apply after gives: a given feature that's switched off is off.

Each change sends its own event (customer-entitlement-given, -taken, -restored) plus customer-set, so your records see every custom deal. See Custom deals.

The customer record​

FieldNotes
id, plan, type, label
refs, alt_idsLists of IDs.
metersEntitlement to meter: usage this period, its cost, holds, and estimates.
overridesEntitlement to limit.
given, takenGives and takes (null until the first).
grantsGrant ID to grant (credit balances).
capsCap ID to spend cap.
metadataYours.

The record is plain JSON (LimitrCustomer in TypeScript). It holds all of the customer's state, so saving it and loading it back restores the customer exactly. See Save customer state.

Reads​

CallReturns
customer(id)The record, or undefined.
customers()Every customer, keyed by ID.
customerMetadata(id)The metadata.
customerRefs(id)The refs.
plan(id)The customer's plan.
features(id, context?)Entitlements it can use now. See Feature rules.
customerMarginSnapshot(id)Revenue, cost, and margin this period. See Cost and margin.

Events​

EventWhen
customer-setCreated, replaced, or changed: plan, overrides, gives and takes, alt IDs, caps, included topups.
customer-removedDeleted.
customer-plan-changedPlan changed (previous_plan, new_plan, customer).
customer-entitlement-given / -taken / -restoredGives and takes.
customer-topup-grantedA topup was applied.

Meter events carry the customer too. See Events.