Skip to main content
Version: 2.x

:::info Executable tutorial

This page is generated from a tested Jupyter notebook. Open it in Google Colab or view the source notebook.

:::

Configure plans and allowances

Plans combine a rate card, allowed operations, entitlements, and optional recurring credit allowances. This tutorial moves one account from a free plan to a paid plan and back while observing admission and balance behavior.

Learning objectives​

After completing this tutorial, you can:

  • Assign and change an account plan
  • Distinguish allowance consumption from balance deductions
  • Enforce operation and feature entitlements
  • Explain what happens when a plan changes mid-cycle

Prerequisites​

  • Complete the credit lifecycle tutorial
  • Start the notebook server from samples/python/notebooks/

Setup​

Start a temporary Postgres store with the standard config. on_account_created lands USER_ADA on the free plan by default.

from decimal import Decimal

from bursar.metrics import UsageMetrics
from shared import cleanup, base_config, publish_config, start_postgres_store, USER_ADA


def completion(output=Decimal(500)):
return UsageMetrics(
operation="completion",
measures={
"input_tokens": Decimal(1000),
"output_tokens": output,
"cache_read_tokens": Decimal(200),
},
dimensions={"model": "gpt-4o"},
)


def execution(jobs=Decimal(1)):
return UsageMetrics(
operation="execution",
measures={"jobs": jobs, "compute_seconds": Decimal(30)},
dimensions={"model": "gpt-4o"},
)


store, pgdata = start_postgres_store()
bursar = publish_config(store, base_config())
bursar.accounts.on_account_created(USER_ADA, "signup")
print("created on plan:", bursar.credits.get_user_plan(USER_ADA).plan_key)

The free allowance​

The free plan grants 10,000 credits per month. check_allowance reports the plan's allowance window: how much is left, and when the period runs. Nothing has been spent yet, so the full 10000.000000 remains.

allowance = bursar.credits.check_allowance(USER_ADA)
print("plan:", allowance.plan_id)
print("allowance remaining:", allowance.allowance_remaining)
print("period:", allowance.period_start, "->", allowance.period_end)

Spending the allowance​

Free completions draw down the allowance, not a wallet: amount charged to the balance is 0.000000, the allowance consumed is 0.000008 per small chat, and no ledger entry is written (entry_id is empty). After 12 chats the allowance shows 9999.999904 remaining — 12 × 0.000008 consumed.

for n in range(12):
r = bursar.credits.deduct(USER_ADA, completion(), idempotency_key=f"free-{n}")
print(f"chat {n + 1:2d}: amount={r.amount} allowance_consumed={r.allowance_consumed} entry_id={r.entry_id!r}")

allowance = bursar.credits.check_allowance(USER_ADA)
print("allowance remaining after 12 chats:", allowance.allowance_remaining)

Entitlements: features behind the plan​

get_user_plan exposes the entitlement map. On the free plan, voice_mode is False and max_context is 128000. Use check_feature as the runtime gate before offering a protected feature.

plan = bursar.credits.get_user_plan(USER_ADA)
print("plan key:", plan.plan_key)
print("entitlements:", plan.entitlements)
print("voice_mode:", plan.entitlements["voice_mode"].value, "| max_context:", plan.entitlements["max_context"].value)
print("check_feature(voice_mode):", bursar.credits.check_feature(USER_ADA, "voice_mode").has_feature)

Upgrading to pro​

set_user_plan swaps the account to pro: voice_mode flips to True, max_context grows to 200000, and the allowance is gone — pro is billed from a prepaid balance instead, so check_allowance returns None — a plan with no credit allowance has no window at all.

bursar.credits.set_user_plan(USER_ADA, "pro")
plan = bursar.credits.get_user_plan(USER_ADA)
print("plan key:", plan.plan_key)
print("voice_mode:", plan.entitlements["voice_mode"].value, "| max_context:", plan.entitlements["max_context"].value)
print("allowance policy:", plan.allowance)

allowance = bursar.credits.check_allowance(USER_ADA)
print("check_allowance on pro:", repr(allowance))

Billing from the balance​

The same completion that drew from the allowance on free now hits the wallet: amount=0.000008 plus a usage ledger entry. Pro also unlocks operations — an execution job is now billable at 0.040000.

bursar.credits.add_credits(USER_ADA, Decimal("100.00"), entry_type="purchase", idempotency_key="pro-topup")
chat = bursar.credits.deduct(USER_ADA, completion(), idempotency_key="pro-chat-1")
print("pro chat: amount", chat.amount, "| balance after", chat.balance_after, "| entry", chat.entry_id[:12])

run = bursar.credits.deduct(USER_ADA, execution(), idempotency_key="pro-run-1")
print("execution job: amount", run.amount, "| balance after", run.balance_after)

Downgrading mid-cycle​

Back to free: the allowance policy is restored and picks up where it left off (9999.999904 — pro usage never touched it), voice_mode is gated again, and execution — an operation the free plan does not allow — is refused with OperationNotAllowedError.

bursar.credits.set_user_plan(USER_ADA, "free")
plan = bursar.credits.get_user_plan(USER_ADA)
print("plan key:", plan.plan_key)
print("allowance remaining:", bursar.credits.check_allowance(USER_ADA).allowance_remaining)
print("voice_mode:", bursar.credits.check_feature(USER_ADA, "voice_mode").has_feature)

try:
bursar.credits.deduct(USER_ADA, execution(), idempotency_key="downgraded-run")
except Exception as exc:
print("execution on free:", type(exc).__name__)