Skip to main content

Policy document

A policy is one document that holds your pricing: the credits you count, the plans customers are on, what each plan includes, and what everything costs. The engine loads it, validates it, and keeps customer state in it.

Shape​

policy: {
credits: {
request: { label: 'API request' }
ai_token: { label: 'AI token', price: { amount: 0.00002 }, overhead_cost: 0.000008 }
}
plans: {
free: {
default: true
entitlements: {
export_csv: {}
api: {
limit: {
credit: 'request', value: 1000
resets: true, reset_inc: 1day
}
}
}
}
pro: {
entitlements: {
export_csv: {}
api: {
limit: {
credit: 'request', value: 100_000
resets: true, reset_sch: 'monthly:1'
}
}
chat: {
limit: {
credit: 'ai_token', mode: 'soft', value: 2_000_000
resets: true
}
}
}
}
}
}

The policy always lives in a field named policy at the root of the document. Plan and credit names are their field names (free, pro, request).

Formats​

Write a policy in whatever your team already uses. These all load the same way:

FormatLoad withNotes
StofLimitr.new(text)The default. A superset of JSON with comments, units (1GB, 30days), and functions.
JSONLimitr.new(textOrObject, 'json')A string or a plain object.
YAMLLimitr.new(text, 'yaml')
TOMLLimitr.new(text, 'toml')
BSTFLimitr.new(bytes, 'bstf')Stof's binary format.

The same policy in JSON and YAML:

{
"policy": {
"credits": { "request": { "label": "API request" } },
"plans": {
"free": {
"default": true,
"entitlements": {
"api": { "limit": { "credit": "request", "value": 1000, "resets": true, "reset_sch": "monthly:1" } }
}
}
}
}
}
policy:
credits:
request:
label: API request
plans:
free:
default: true
entitlements:
api:
limit:
credit: request
value: 1000
resets: true
reset_sch: monthly:1

Only Stof can hold functions (cost functions, enabled rules, notifications). A JSON, YAML, or TOML policy is plain data, and durations are numbers of milliseconds (86400000 for a day). Units in strings work in every format ("value": "1GB").

Top-level fields​

FieldTypeDefaultNotes
creditsobj{}Credit name to Credit.
plansobj{}Plan name to Plan.
exchangeExchange{}Currencies, credit conversion, and the order grants are spent in. See Exchange.
notificationsobj{}Name to Notification.
capabilitiesobj{}Name to Capability. See AI tools.
estimate_min_samplesint5A customer's own usage estimate is used once it has this many calls. See Reservations.
hold_ttlms10minHow long an unsettled reservation holds room.
versionverengine versionSet by the engine.

The engine also keeps its state here: customers (customer ID to Customer), alt_customer_ids, and estimates (policy-wide usage estimates). You don't write these by hand; they're filled as customers are created and used, and you can save and load them (see Save customer state).

Validation​

Limitr.new validates the policy when it loads and throws with a plain message when something is wrong:

A credit named "requests" does not exist in this policy

Validation checks every credit, plan, entitlement, limit, topup, and exchange pair against its type, sorts and checks price tiers, and fills defaults (Ex. a limit that resets with no period gets 30 days). To check a policy yourself, Ex. in CI before shipping a pricing change:

const [ok, message] = await limitr.valid();

From Stof, policy.valid() returns a bool and the message is in <LimitrValidation>.error_message. Pass validate = false to Limitr.new to skip it (not recommended: an invalid policy fails at call time instead).

Types fill in the rest​

When the policy loads, the engine reads each part with its Stof type: the policy is a Limitr, each credit a Credit, each plan a Plan, and so on. Every type has a default for every field, which is why a limit only needs credit and value, and why exporting a limit shows far more fields than you wrote. The types live in the engine source (src/spec/*.stof in the Limitr repo).

Changing a policy at runtime​

The engine holds the policy in memory, so you can change parts of it while it runs:

  • setPlan(id, stof) and deletePlan(id) add, replace, or remove a plan.
  • setNotifications(stof) and setCapabilities(stof) add notifications and capabilities.
  • With Limitr Cloud, you edit the policy in Cloud and every running engine receives the new version without a deploy.

Customer state stays put when a plan changes: meters keep their values, and the next call uses the new limits.