Skip to main content
Version: 2.x

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:

VariableRequired for
BURSAR_MIGRATION_DATABASE_URLmigrate; use a dedicated migration owner or administrator
BURSAR_OPERATOR_DATABASE_URLtenant create, tenant status, and the provisioning half of tenant bootstrap
DATABASE_URLDatabase-backed config commands, the config half of tenant bootstrap, and the application runtime
BURSAR_TENANT_IDDatabase-backed config commands and tenant bootstrap without --id; not local validate or schema
BURSAR_PROVIDER_ENVIRONMENTEvery 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

Terminal
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:

PostgreSQL
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:

Terminal
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:

Terminal
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:

Terminal
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:

Terminal
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:

Terminal
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:

rollout.yaml
plans:
pro:
effective: next_renewal
include_pinned: false

Pass it while publishing or activating:

Terminal
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.