Skip to main content
Version: 2.x

Plans and access control

A plan bundles everything a paid tier gets: which operations are allowed, which rate card prices them, what credits come free, which features unlock, what usage is limited, and how concurrency is gated. Plans live in the canonical config and are enforced by the database — policy is never only application code.

What a plan contains

FieldRole
display_nameCustomer-facing label
rankCatalog ordering; lower ranks first (default 0)
rate_cardThe rate card that prices allowed operations
allowed_operationsOperations the plan may run
featuresFeature values for the plan ({ voice_mode: true, max_context: 200000 })
credit_allowanceFree credits per window; its priority positions them among credit buckets
quotasPer-operation measure limits over a window
credit_policyReference to a prepaid or credit_line policy
admission_policyReference to a named concurrency policy
revision_policyWhen config changes reach subscribers (immediate | next_renewal | pinned)

catalog.default_plan names the signup plan; when omitted, the lowest-rank plan is chosen.

Allowances

A credit_allowance grants free credits that reset on a window — the free plan's monthly 10,000 credits:

credits:
buckets:
promotional: { priority: 1 }
purchased: { priority: 10 }
default_bucket: purchased

plans:
free:
display_name: Free
rank: 0
rate_card: standard
allowed_operations: [completion]
credit_allowance:
amount: "10000"
priority: 5
window: { type: calendar, unit: month, count: 1 }

Windows are calendar (aligned to a timezone), rolling (a duration since first use), or plan_assignment (anchored to when the plan was assigned).

Allowance and bucket priorities use one ordering namespace; lower numbers spend first. In the example, promotional credits spend first, then the monthly allowance, then purchased credits. Every allowance must declare a priority, and that value cannot equal a bucket priority. The allowance remains a plan entitlement with its own reset window rather than a synthetic bucket.

DeductionResult.allowance_consumed reports how much of a charge the allowance covered. check_allowance(user_id) returns the current window (plan_id, allowance_remaining, period_start, period_end) for display and gating. unset_user_plan pauses the allowance window; reassigning re-anchors it.

Quotas

A quota limits one measure of one operation over a window — the pro plan's daily output-token cap:

quotas:
daily_tokens:
operation: completion
measure: output_tokens
limit: "500000"
window: { type: calendar, unit: day, count: 1 }
enforcement: block
emit_at_percent: [80, 100]

enforcement is block (the charge fails with QuotaExceededError) or allow (usage records, the balance still pays). emit_at_percent fires credits.quota_threshold events as usage crosses each percentage (plus credits.quota_blocked when a block fires); persisted events are listed with list_quota_events, and current windows with get_quota_state. Quotas are checked in the same atomic transaction as the deduction, so a race cannot slip usage past a block.

Features and entitlements

entitlements.features declares typed product features with defaults; check_feature(user_id, feature) returns the plan's value with a has_feature flag. Presence is distinguished from truthiness: true, any number (including 0), and any string (including "") count as present; false, null, and absence do not.

entitlements:
features:
voice_mode: { type: boolean, default: false }
max_context:
{ type: integer, default: 128000, minimum: 8000, maximum: 200000 }

Feature types are boolean, enum (with values), integer (with optional minimum/maximum), and string (with optional pattern). Operations can require a feature at charge time: pass feature="voice_mode" to deduct/reserve/settle and the store rejects the call with FeatureNotEntitledError when the plan lacks it. The database, not the application, remains the gate.

Admission policies

admission.policies name reusable concurrency limits — a global max_in_flight plus per-operation overrides:

admission:
policies:
default: { max_in_flight: 4 }

A plan references one policy (pro uses default). reserve enforces the limit; exceeding it raises ConcurrencyLimitError before any hold is taken. The same policy can be shared by several plans.

Credit policies

credits.policies name prepaid or credit-line policies:

PolicyBehavior
prepaidFloor at zero; structural zero debt
credit_linelimit allows a negative floor — a bounded overdraft

A plan's credit_policy reference applies its floor to deductions. The constructor preset (strict_prepaid by default) is the fallback for planless users.

Plan assignment

Account creation assigns catalog.default_plan, or the lowest-rank plan when no default is declared, and applies eligible account_created grants. Runtime changes update the assignment, re-anchor the applicable policy windows, and emit a plan-change event.

Large installations use resumable, bounded plan-migration batches instead of one unbounded transaction. Use the generated credits-service reference for exact assignment and migration signatures.

Revision policy

When a new catalog version activates, revision_policy decides when subscribers move: immediate applies the change at once, next_renewal waits for the next subscription renewal, and pinned keeps the subscriber on the version in effect at assignment. When omitted, subscription-backed plans use next_renewal and other plans use immediate.