Bursar CLI reference
The bursar CLI is the operator interface for migrations, tenant lifecycle,
and the canonical configuration. Connection values and the financial
environment come from environment variables, never the command line:
| Variable | Required for |
|---|---|
BURSAR_MIGRATION_DATABASE_URL | migrate; use a dedicated migration owner or administrator |
BURSAR_OPERATOR_DATABASE_URL | tenant create, tenant status, and the provisioning half of tenant bootstrap |
DATABASE_URL | Database-backed config commands, the config half of tenant bootstrap, and the application runtime |
BURSAR_TENANT_ID | Database-backed config commands and tenant bootstrap without --id; not local validate or schema |
BURSAR_PROVIDER_ENVIRONMENT | Every database-backed config command and tenant bootstrap; exactly live, test, or sandbox |
config validate and config schema are local operations. They do not need a
database connection, tenant ID, or provider environment.
Migrate
export BURSAR_MIGRATION_DATABASE_URL=postgresql://bursar_migrator@db.example.com/bursar
bursar migrate
The command applies pending ordered SQL files, records checksums, and fails if an already-applied file changed. A second run is a no-op.
Provision caller principals
After the first migration, create separate application and operator logins as the migration owner. Set their passwords with your database provider or secret tooling instead of placing credentials in shell history:
CREATE ROLE bursar_app
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS;
GRANT bursar_client TO bursar_app WITH INHERIT FALSE, SET TRUE;
CREATE ROLE bursar_ops
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS;
GRANT bursar_operator TO bursar_ops WITH INHERIT FALSE, SET TRUE;
Give each login exactly one Bursar caller role. The SDK enters
bursar_client only for tenant-scoped transactions; the tenant commands enter
bursar_operator only for their operator transaction. Do not use a superuser,
BYPASSRLS role, Supabase service_role, or one dual-role login as the
application runtime.
Tenants
Provision tenants with the dedicated operator connection:
export BURSAR_OPERATOR_DATABASE_URL=postgresql://bursar_ops@db.example.com/bursar
bursar tenant create acme \
--id 018f7f5f-7b4a-7000-8000-000000000001 \
--display-name "Acme"
tenant create generates the UUID when --id is omitted and prints it.
Change lifecycle state with tenant status:
bursar tenant status 018f7f5f-7b4a-7000-8000-000000000001 suspended
bursar tenant status 018f7f5f-7b4a-7000-8000-000000000001 active
To initialize an embedded Bursar tenant, provision it and publish its initial config through one idempotent command:
export DATABASE_URL=postgresql://bursar_app@db.example.com/bursar
export BURSAR_TENANT_ID=018f7f5f-7b4a-7000-8000-000000000001
export BURSAR_PROVIDER_ENVIRONMENT=test
bursar tenant bootstrap acme ./pricing.yaml \
--display-name "Acme"
tenant bootstrap validates the config before provisioning. Tenant creation
and config publication are each idempotent, so the command is safe to retry
after an operational failure. It intentionally needs both the operator and
application URLs and an explicit provider environment: provisioning runs as
bursar_operator, while config publication uses the tenant-scoped
bursar_client path within that financial namespace. Host applications should
use these operator commands instead of writing Bursar-owned tables.
Config lifecycle
Catalog versions are immutable and managed per tenant through bursar config.
Database-backed subcommands require the application DATABASE_URL and
BURSAR_TENANT_ID plus an explicit BURSAR_PROVIDER_ENVIRONMENT. Validate or
print the schema locally without any of those values:
bursar config validate ./pricing.yaml # validate without applying (--json for CI)
bursar config schema # print the config JSON Schema
Then select the database-backed financial namespace:
export DATABASE_URL=postgresql://bursar_app@db.example.com/bursar
export BURSAR_TENANT_ID=018f7f5f-7b4a-7000-8000-000000000001
export BURSAR_PROVIDER_ENVIRONMENT=test
bursar config set ./pricing.yaml # publish + activate a new version (--label, --rollout)
bursar config get # print the active version as JSON
bursar config list # list all versions (* = active)
bursar config activate 3 # switch the active version (--rollout)
bursar config pin <subject-uuid> # hold one current assignment on its revision
bursar config pin <subject-uuid> --unpin # remove that hold
bursar config apply-due --limit 100 # apply due renewal-effective changes
bursar config export 3 # dump one version as JSON
bursar config diff 2 3 # unified diff between two versions
config set always creates a new version and no-ops when the payload is
identical to the active one — the command reports "No changes" and does not
churn versions. validate, set, and tenant bootstrap accept - to read
from stdin. list marks the active version with * and shows labels and
timestamps; diff compares two canonicalized versions and schema prints
the JSON Schema for editor autocompletion and CI validation. export takes
the version number and dumps that one immutable version as JSON.
Per-release rollout
Each plan's evolution.default_rollout controls normal revision adoption. To
override selected plans for one catalog publication, save a rollout manifest:
plans:
pro:
effective: next_renewal
include_pinned: false
Pass it while publishing or activating:
bursar config set ./pricing.yaml --rollout ./rollout.yaml
bursar config activate 3 --rollout ./rollout.yaml
effective is immediate, next_renewal, or new_assignments_only.
next_renewal is valid only for a plan referenced by a subscription offer.
Pinned assignments are excluded unless include_pinned is true. Run
config apply-due from a bounded background job to advance changes whose
renewal time has arrived.
The CLI deliberately stops at Bursar-owned schema, tenant, and catalog operations. The embedding application owns process supervision, scheduling, database availability and recovery, secret delivery, and every provider or cloud deployment decision.