Bursar is Zonastery's open-source credit-ledger and billing SDK for AI SaaS platforms. It meters usage, prices operations, manages balances, and bills customers from one canonical PostgreSQL schema and one versioned configuration document. The Python, TypeScript, and Go SDKs share both, so they produce identical accounting and identical bills.
- Canonical accounting —
credit_accounts.balanceis the only stored account balance andcredit_ledger_entriesthe only monetary history; per-bucket-lot spend counters incredit_lotsare the only derived projection, so there are no projected transaction or bucket-balance tables. - Declarative configuration — operations, rate cards, plans, allowances, and billing live in one strict, versioned document, published through the SDK and readable by billing and auto-recharge.
- Financial safety by default — reserve-then-settle leases with idempotency keys, expiry, and strict-prepaid or overdraft policies.
- Safe expressions — an AST-based evaluator with a strict allowlist: no
eval, no arbitrary code execution. - Identical behavior in Python, JavaScript, and Go — same config, same rounding, same results.
Requirements: Python 3.12 or 3.13, Node.js 22+, or Go 1.25+; PostgreSQL 16+, pg_partman 5.x, and pg_jsonschema 0.3+ available to the database migration role.
python -m pip install "bursar[postgres]"export BURSAR_MIGRATION_DATABASE_URL=postgresql://bursar_migrator@db.example.com/bursar
bursar migratebursar migrate applies the ordered SQL baseline and records checksums, so it
is safe to re-run. Run it as a dedicated migration principal, not with the
application's runtime credentials.
Go applications use the same Python-owned migration CLI and install the versioned SDK module separately:
go get github.com/Zonastery/bursar/golang/v2Create a tenant, then build the facade:
export BURSAR_OPERATOR_DATABASE_URL=postgresql://bursar_ops@db.example.com/bursar
bursar tenant create acme --id 018f7f5f-7b4a-7000-8000-000000000001
export DATABASE_URL=postgresql://bursar_app@db.example.com/bursar
export BURSAR_TENANT_ID=018f7f5f-7b4a-7000-8000-000000000001Provision bursar_ops and bursar_app as separate SET-only members of
bursar_operator and bursar_client, respectively. The
CLI guide has the exact SQL and
caller-role contract; never use migration-owner or BYPASSRLS credentials in
the application.
importosfromdecimalimportDecimalfrombursarimportBursar, PostgresStorestore=PostgresStore(
os.environ["DATABASE_URL"],
tenant_id=os.environ["BURSAR_TENANT_ID"],
provider_environment="test",
)
bursar=Bursar(credit_store=store)
added=bursar.credits.add_credits(
user_id,
Decimal("1000"),
entry_type="purchase",
idempotency_key="checkout:order-42",
)
charged=bursar.credits.deduct_credits(
user_id,
Decimal("25"),
idempotency_key="request:job-42",
)
entry=bursar.credits.get_ledger_entry(user_id, charged.entry_id)import{Bursar,PostgresStore}from"@zonastery/bursar";constbursar=newBursar({creditStore: newPostgresStore({postgres: process.env.DATABASE_URL!,
tenantId,providerEnvironment: "test",}),});constadded=awaitbursar.credits.addCredits(userId,"1000",{type: "purchase",idempotencyKey: "checkout:order-42",});constcharged=awaitbursar.credits.deductCredits(userId,"25",{idempotencyKey: "request:job-42",});constentry=awaitbursar.credits.getLedgerEntry(userId,charged.entryId);package main
import (
"context""log""os"
bursar "github.com/Zonastery/bursar/golang/v2"
)
funcmain() {
ctx:=context.Background()
store, err:=bursar.NewPostgresStore(ctx, os.Getenv("DATABASE_URL"), bursar.PostgresStoreOptions{
TenantID: os.Getenv("BURSAR_TENANT_ID"),
ProviderEnvironment: bursar.ProviderEnvironmentTest,
})
iferr!=nil {
log.Fatal(err)
}
deferstore.Close()
sdk, err:=bursar.New(bursar.Options{CreditStore: store})
iferr!=nil {
log.Fatal(err)
}
iferr:=sdk.LoadCatalog(ctx); err!=nil {
log.Fatal(err)
}
}Pricing is one strict, versioned document. Publish and activate it through the facade:
version: 1pricing:
operations:
completion:
measures:
{ input_tokens: { unit: token }, output_tokens: { unit: token } }dimensions: { model: { type: string } }rate_cards:
standard:
operations:
completion:
unmatched:
action: chargecharge:
type: sumcomponents:
- { type: per_unit, measure: input_tokens, rate: "0.05" }
- { type: per_unit, measure: output_tokens, rate: "0.10" }credits:
buckets:
purchased: { priority: 10 }default_bucket: purchasedbursar.catalog.publish_and_activate(config)awaitbursar.catalog.publishAndActivate(config);The full documentation — concepts, guides, CLI, and API references — is at https://zonastery.github.io/bursar/.
- Python package —
bursaron PyPI - TypeScript and JavaScript package —
@zonastery/bursaron npm - Go package —
github.com/Zonastery/bursar/golang/v2on pkg.go.dev - Build a prepaid credit system for AI SaaS
- Changelog
- Citation metadata
- Contributing
Bursar publishes the same maintained documentation in formats designed for coding agents and retrieval systems:
llms.txt— ordered map of the canonical documentationllms-full.txt— complete documentation corpus- Bursar Agent Skill — install with
npx skills add zonastery/bursar@bursar - Context7 and DeepWiki — external indexes
When an indexed answer and an installed package disagree, use the documentation for the installed version or the matching release tag as the source of truth.
AGPL-3.0. See LICENSE.
