Skip to main content
Version: 2.x

bursar.CreditStore

class bursar.CreditStore

Bases: ABC

Interface for credit storage backends.

PostgresStore is the production adapter. Custom stores implement this interface to back Bursar with their own storage.

close()

Release resources owned by the store.

Stateless/custom stores need no cleanup. Connection-backed stores should override this method so applications can close them through the Bursar facade instead of retaining an adapter-specific reference.

  • Return type: None

abstractmethod get_balance(user_id)

Return current balance and lifetime purchased amount.

  • Parameters: user_id (str)
  • Return type: BalanceResult

abstractmethod add_credits(user_id, amount, type='adjustment', metadata=None, expires_at=None, bucket=None, *, idempotency_key)

Atomically add credits and log a transaction.

  • Parameters:
    • amount (Decimal) – Fractional credit amount (Decimal).
    • expires_at (datetime | None) – Optional datetime after which the credits expire.
    • bucket (str | None) – Optional bucket key to grant into. When no buckets are configured, must be None or "default". When buckets are configured and omitted, resolves to the bucket with is_default=True (raises if none is marked default).
    • idempotency_key (str) – Required user-scoped replay key. A retried grant with the same key (e.g. a webhook redelivered by the sender) returns the original entry’s result rather than granting a second time — no double-mutation, no second ledger row. Follows the same replay idiom already used by deduct_with_allowance()/settle_lease()/ deduct_team().
    • user_id (str)
    • type (str)
    • metadata (CreditMetadata | None)
  • Return type: AddCreditsResult

abstractmethod deduct_with_allowance(user_id, amount, *, idempotency_key, operation='usage', feature=None, model=None, region=None, measures=None, dimensions=None, metadata=None)

Atomically charge a gross cost in a single server-side transaction.

This is the canonical “calculate cost then charge now” path. Within one transaction the store:

  1. Locks the user’s credit row.
  2. Honors idempotency_key (user-scoped) — a replay returns the : original result with idempotent=True. The replayed balance_after is the balance at the time of the original call, not the current balance.
  3. Consumes free allowance first (allowance_consumed on the result), : charging only the net remainder to the balance.
  4. Enforces the canonical plan credit policy and quotas server-side.
  5. Debits the balance and inserts one usage transaction.

All-or-nothing: any failure rolls back allowance consumption and the balance change. Business failures are returned via DeductionResult.error (the manager maps codes to exceptions); the store does not import manager-level exceptions.

  • Parameters:
    • user_id (str) – The user to charge.
    • amount (Decimal) – Gross cost (Decimal, >= 0, fractional 6dp).
    • idempotency_key (str) – Optional user-scoped replay key.
    • operation (str) – Catalog operation key used for plan-aware policy.
    • feature (str | None) – Optional feature key used for entitlement checks.
    • model (str | None) – Optional model name recorded on the transaction.
    • region (str | None) – Optional deployment region recorded on the transaction.
    • measures (dict *[*str , Decimal | int | float ] | None) – Usage measures evaluated by quota rules.
    • dimensions (dict *[*str , Any ] | None) – Usage dimensions evaluated by pricing/policy rules.
    • metadata (CreditMetadata | None) – Extra metadata merged onto the transaction.
  • Returns: DeductionResult with net amount, allowance_consumed, balance_after, idempotent, and error.
  • Return type: DeductionResult

abstractmethod create_lease(user_id, amount, operation_type, options)

Atomically acquire a lease (hold) — the only authoritative admission control.

Under one lock the store: (1) ensures the balance row exists; (2) enforces max_concurrent by counting active leases for (user_id, operation_type); (3) enforces canonical entitlements and quotas; (4) computes available = balance − Σ active holds and rejects with error="insufficient_credits" if available − amount < floor; (5) inserts an active lease expiring after ttl_seconds.

floor is the resolved admission floor (>= 0 for strict; the negative overdraft_floor for overdraft). billing_mode/overdraft_floor are persisted on the lease for settle-time/observability. Business failures are returned via LeaseResult.error; the store never raises domain exceptions.

  • Parameters:
    • user_id (str)
    • amount (Decimal)
    • operation_type (str)
    • options (CreateLeaseOptions)
  • Return type: LeaseResult

abstractmethod settle_lease(user_id, lease_id, amount, options=None)

Charge the actual cost against a lease, then mark it settled.

De-clamped: charges amount even if it exceeds the lease hold (overdraft), never clamps to the lease amount. Pipeline: idempotency replay → allowance consumption → quota accounting → debit (the balance may go negative in overdraft) → ledger row → mark the lease settled. amount == 0 releases the lease without charging.

Lease-state failures are returned via DeductionResult.error: lease_not_found (missing / other user / released) or lease_expired (the lease TTL elapsed). A replayed settle (same idempotency key, or a re-settle of an already-settled lease) returns the original result with idempotent=True.

  • Parameters:
    • user_id (str)
    • lease_id (str)
    • amount (Decimal)
    • options (SettleLeaseOptions | None)
  • Return type: DeductionResult

abstractmethod get_lease_pricing_context(user_id, lease_id)

Return the catalog revision and rate card captured by a lease.

Usage-metric settlement must price against this immutable context rather than the subject’s current plan, which may have changed after admission. None means the lease is missing or does not belong to user_id.

  • Parameters:
    • user_id (str)
    • lease_id (str)
  • Return type: LeasePricingContext | None

abstractmethod release_lease(user_id, lease_id)

Release a lease without charging (work failed/aborted).

Idempotent and safe on missing or already-finalized leases: transitions an active/expired lease to released and reports released=True; otherwise reports released=False with a reason.

  • Parameters:
    • user_id (str)
    • lease_id (str)
  • Return type: ReleaseResult

abstractmethod renew_lease(user_id, lease_id, ttl_seconds)

Extend an active lease without changing its captured policy.

  • Parameters:
    • user_id (str)
    • lease_id (str)
    • ttl_seconds (int)
  • Return type: LeaseResult

expire_leases(limit=100)

Expire a bounded batch of abandoned leases and release reservations.

  • Parameters: limit (int)
  • Return type: int

abstractmethod get_available(user_id)

Advisory, non-locking read of available = balance − Σ active holds.

For UI only — never an admission gate; the value may be stale the instant it is read.

  • Parameters: user_id (str)
  • Return type: AvailableResult

abstractmethod get_bucket_balances(user_id)

Return per-bucket balance breakdown for a user, ordered by priority ascending.

When no buckets are configured, returns a single synthetic "default" bucket entry so the shape is uniform regardless of whether buckets are configured.

  • Parameters: user_id (str)
  • Return type: BucketBalancesResult

execute_grant_program(request)

Execute one configured grant-program event.

  • Parameters: request (ExecuteGrantProgramRequest)
  • Return type: list[GrantProgramAwardResult]

abstractmethod get_active_catalog()

Fetch the active catalog revision from the store.

  • Return type: CatalogRevision | None

abstractmethod publish_and_activate_catalog(config, label=None, rollout=None)

Publish and activate a catalog revision.

Deactivates the previous active config and inserts a new one. Returns the new config id.

  • Parameters:
    • config (dict *[*str , Any ])
    • label (str | None)
    • rollout (CatalogRollout | dict *[*str , Any ] | None)
  • Return type: str

abstractmethod get_catalog_history()

List catalog revisions, newest first.

  • Return type: list[CatalogRevisionSummary]

abstractmethod get_catalog_revision(version)

Fetch a catalog revision by version number.

  • Parameters: version (int)
  • Return type: CatalogRevision | None

abstractmethod activate_catalog_revision(version, rollout=None)

Activate a catalog revision (deactivates all others).

  • Parameters:
    • version (int) – The version number to activate.
    • rollout (CatalogRollout | dict *[*str , Any ] | None)
  • Returns: The activated config id.
  • Return type: str

abstractmethod publish_catalog_draft(config, label=None)

Publish an inactive catalog draft without changing the live catalog.

  • Parameters:
    • config (dict *[*str , Any ])
    • label (str | None)
  • Return type: str

abstractmethod get_user_plan(user_id)

Fetch user’s current plan (including feature entitlements).

  • Parameters: user_id (str)
  • Return type: GetUserPlanResult

check_feature(user_id, feature)

Check whether a user’s plan has a specific feature entitlement.

Convenience method. Default implementation calls get_user_plan() and inspects the features dict. Override in custom stores for optimized queries.

Feature presence is distinguished from truthiness: the feature is considered present when the key exists and its value is not None/False. Numeric 0 and empty string "" are therefore present (has_feature=True).

  • absent / None / Falsehas_feature=False
  • True / numeric (incl. 0) / string (incl. "") → has_feature=True

Note: identity checks (is None/is False) are used rather than the contract’s literal not in (None, False), because 0 == False / 0.0 == False in Python would otherwise mis-classify numeric 0 as absent even though numeric 0 and "" are present values.

  • Parameters:
    • user_id (str)
    • feature (str)
  • Return type: CheckFeatureResult

abstractmethod set_user_plan(user_id, plan_key, plan_assigned_at=None)

Assign a plan to a user.

plan_assigned_at anchors plan-assignment policy windows. When omitted, the store uses the current time.

  • Parameters:
    • user_id (str)
    • plan_key (str)
    • plan_assigned_at (datetime | None)
  • Return type: SetUserPlanResult

abstractmethod unset_user_plan(user_id)

Clear the user’s plan assignment.

  • Parameters: user_id (str)
  • Return type: UnsetUserPlanResult

abstractmethod set_plan_revision_pin(user_id, pinned)

Pin or unpin the current assignment’s catalog revision.

  • Parameters:
    • user_id (str)
    • pinned (bool)
  • Return type: bool

abstractmethod apply_due_plan_changes(limit=100)

Apply a bounded batch of scheduled plan changes that are now due.

  • Parameters: limit (int)
  • Return type: int

abstractmethod start_plan_migration(from_plan_id, to_plan_id)

Create a resumable migration from one catalog plan to another.

  • Parameters:
    • from_plan_id (str | None)
    • to_plan_id (str)
  • Return type: PlanMigrationStartResult

abstractmethod migrate_plan_batch(migration_id, batch_size=100)

Advance a plan migration by one bounded batch.

  • Parameters:
    • migration_id (str)
    • batch_size (int)
  • Return type: PlanMigrationBatchResult

abstractmethod get_quota_state(user_id, quota_key=None)

Return current quota windows for a user.

  • Parameters:
    • user_id (str)
    • quota_key (str | None)
  • Return type: list[QuotaState]

abstractmethod check_allowance(user_id)

Get the database-owned current allowance window.

  • Parameters: user_id (str)
  • Return type: AllowanceResult | None

abstractmethod list_quota_events(user_id, options=None)

List persisted quota threshold and blocking events.

  • Parameters:
    • user_id (str)
    • options (ListQuotaEventsOptions | None)
  • Return type: list[QuotaEvent]

abstractmethod refund_credits(entry_id, *, idempotency_key, amount=None, reason=None, metadata=None)

Refund a previous credit deduction.

  • Parameters:
    • entry_id (str) – The transaction to refund.
    • amount (Decimal | None) – Optional partial refund amount. Full refund if omitted.
    • reason (str | None) – Optional reason for the refund.
    • metadata (CreditMetadata | None) – Extra metadata to attach to the refund entry.
    • idempotency_key (str) – Required stable replay key.
  • Returns: RefundResult with the refund ledger entry details, or error set if the transaction doesn’t exist or is already refunded.
  • Return type: RefundResult

abstractmethod sweep_expired_credits(dry_run=False, user_id=None, limit=100)

Expire at most limit eligible credit lots.

  • Parameters:
    • dry_run (bool)
    • user_id (str | None)
    • limit (int)
  • Return type: SweepResult

abstractmethod revoke_credits_by_entry_type(user_id, entry_type)

Revoke all credits of a given transaction type for a user (LIFO across tiers).

Used by the subscription lifecycle to replace cycle-grant credits on renewal. Returns the revoked amount and resulting committed balance.

  • Parameters:
    • user_id (str)
    • entry_type (str)
  • Return type: RevokeCreditsResult

spend_by_user(start, end)

Aggregate spend by user in a time window.

  • Parameters:
    • start (datetime) – Start of time window (inclusive).
    • end (datetime) – End of time window (inclusive).
  • Returns: List of SpendByUserRow with totals per user.
  • Return type: list[SpendByUserRow]

spend_by_model(start, end)

Aggregate spend by model in a time window.

  • Parameters:
    • start (datetime) – Start of time window (inclusive).
    • end (datetime) – End of time window (inclusive).
  • Returns: List of SpendByModelRow with totals per model.
  • Return type: list[SpendByModelRow]

top_users(limit, start, end)

Top users by spend in a time window.

  • Parameters:
    • limit (int) – Maximum number of users to return.
    • start (datetime) – Start of time window (inclusive).
    • end (datetime) – End of time window (inclusive).
  • Returns: List of TopUserRow sorted by total_spend descending.
  • Return type: list[TopUserRow]

daily_spend(start, end)

Daily spend aggregation in a time window.

  • Parameters:
    • start (datetime) – Start of time window (inclusive).
    • end (datetime) – End of time window (inclusive).
  • Returns: List of DailySpendRow with per-day totals.
  • Return type: list[DailySpendRow]

aggregate_stats(start, end)

Aggregate statistics across all users in a time window.

  • Parameters:
    • start (datetime) – Start of time window (inclusive).
    • end (datetime) – End of time window (inclusive).
  • Returns: AggregateStats with total credits consumed, active users, average daily spend, top model, and top user.
  • Return type: AggregateStats

list_ledger_entries(user_id, entry_types=None, from_date=None, to_date=None, limit=50, cursor=None)

List account ledger history with a stable timestamp-plus-entry cursor.

  • Parameters:
    • user_id (str)
    • entry_types (list *[*str ] | None)
    • from_date (datetime | None)
    • to_date (datetime | None)
    • limit (int)
    • cursor (LedgerCursor | None)
  • Return type: LedgerPage

list_usage_entries(user_id, from_date=None, to_date=None, limit=50, cursor=None)

List usage ledger entries with the same cursor contract.

  • Parameters:
    • user_id (str)
    • from_date (datetime | None)
    • to_date (datetime | None)
    • limit (int)
    • cursor (LedgerCursor | None)
  • Return type: LedgerPage

list_usage_charges(user_id, from_date=None, to_date=None, limit=50, cursor=None, include_record_only=True)

List metered usage charges, including allowance-covered events.

  • Parameters:
    • user_id (str)
    • from_date (datetime | None)
    • to_date (datetime | None)
    • limit (int)
    • cursor (UsageChargeCursor | None)
    • include_record_only (bool)
  • Return type: UsageChargePage

record_usage(user_id, operation, requested, *, idempotency_key, feature=None, model=None, region=None, metadata=None, measures=None, dimensions=None)

Append priced usage telemetry without debiting the account again.

  • Parameters:
    • user_id (str)
    • operation (str)
    • requested (Decimal)
    • idempotency_key (str)
    • feature (str | None)
    • model (str | None)
    • region (str | None)
    • metadata (CreditMetadata | None)
    • measures (dict *[*str , Any ] | None)
    • dimensions (dict *[*str , Any ] | None)
  • Return type: UsageRecordResult

get_ledger_entry(user_id, entry_id)

Return one ledger entry when it belongs to the user account.

  • Parameters:
    • user_id (str)
    • entry_id (str)
  • Return type: LedgerEntry | None

create_team(owner_subject_id, name, initial_balance=Decimal('0'), *, idempotency_key)

Create a team with a shared credit balance pool.

  • Parameters:
    • owner_subject_id (str) – Subject that owns the team.
    • name (str) – Human-readable team name.
    • initial_balance (Decimal) – Starting credit balance.
    • idempotency_key (str) – Caller-owned replay key for this creation request.
  • Returns: CreateTeamResult with the new team id.
  • Return type: CreateTeamResult

get_team_balance(team_id)

Fetch team balance and member count.

  • Parameters: team_id (str) – The team’s UUID.
  • Returns: TeamBalanceResult with balance and member count, or None when the team does not exist.
  • Return type: TeamBalanceResult | None

add_team_member(team_id, user_id, role='member', spend_cap=None)

Add a user to a team.

  • Parameters:
    • team_id (str) – The team’s UUID.
    • user_id (str) – The user’s UUID.
    • role (Literal [ 'owner' , 'admin' , 'member' ]) – Member role (e.g. “member”, “admin”).
    • spend_cap (Decimal | None) – Optional per-user spend cap.
  • Returns: AddTeamMemberResult confirming membership.
  • Return type: AddTeamMemberResult

get_team_members(team_id)

List all members of a team.

  • Parameters: team_id (str) – The team’s UUID.
  • Returns: List of TeamMember.
  • Return type: list[TeamMember]

remove_team_member(team_id, user_id)

Remove a team member unless they are the final owner.

  • Parameters:
    • team_id (str)
    • user_id (str)
  • Return type: bool

deduct_team(team_id, user_id, amount, metadata=None, *, idempotency_key)

Deduct credits from a team pool, attributed to a user.

  • Parameters:
    • team_id (str) – The team’s UUID.
    • user_id (str) – The user to attribute the deduction to.
    • amount (Decimal) – Credits to deduct (Decimal).
    • metadata (CreditMetadata | None) – Extra metadata.
    • idempotency_key (str) – Required replay key. A retried team deduction with the same key returns the original result rather than charging the shared pool again.
  • Returns: TeamDeductionResult with ledger entry details.
  • Return type: TeamDeductionResult