Skip to content

Admin Credits

Get Credit Rate Card

Every published version of the card, plus the live margin readout.

margins is what makes credits_per_usd safe to edit: it is one number that reprices the whole product, and the only way to see what it does is against the price points the plans actually sell at.

Responses

StatusDescriptionBody
200Successful ResponseCreditRateCardResponse
422Validation ErrorHTTPValidationError

Publish Credit Rate Card

Publish a new card version. Existing versions are never mutated.

Appends to the stored history rather than replacing it, so every burn receipt that cites an older card_version still reconstructs to the same number and re-deriving history from the ledger keeps reconciling (plan §3.1, §13.1 scenario 11). with_card replaces by effective_from, so re-publishing the same date is a correction of an unstarted version rather than a second entry — publishing a change means a new date.

Request body (required)

FieldTypeRequiredDescription
credits_per_usdnumber | stringyes
effective_fromstring (date)yes
rulesCreditRuleInput[]no
versionintegeryes

Responses

StatusDescriptionBody
200Successful ResponseCreditRateCardResponse
422Validation ErrorHTTPValidationError

Preview Mode Presets

Burn ratios the submitted presets would produce, without saving them.

Plan §7.4: re-pointing mode_normal at a dearer model changes every credit org’s burn rate at once, and because normal is the denominator of every ratio, re-pointing it rescales all of them. The save path in admin_ai.py already writes its own audit row with the resulting ratios; what was missing is seeing them before committing, which is this.

Read-only, so it deliberately writes no audit row — the save does.

Request body (required)

FieldTypeRequiredDescription
mode_high_effortobjectno
mode_normalobjectno
mode_trivialobjectno

Responses

StatusDescriptionBody
200Successful ResponseModePresetPreviewResponse
422Validation ErrorHTTPValidationError

Get Org Credits

Balance, subscription and resolved entitlements for one org.

reconciled is the invariant the whole ledger design rests on: the cached balance — both buckets summed — must equal the sum of every ledger row. False means something wrote credit_balances without a ledger row, which is a bug worth chasing before believing any other number on the screen.

Do not read purchase_total as the target for purchased_balance. It is what the org has bought; the bucket is what is left of it after burns spilled into it, and the two are equal only for an org that has bought and not spent.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseOrgCreditsResponse
422Validation ErrorHTTPValidationError

POST /api/admin/orgs/{org_id}/credits/adjust

Section titled “POST /api/admin/orgs/{org_id}/credits/adjust”

Adjust Org Credits

Move an org’s balance by a signed delta, with a mandatory reason.

Writes two records, and both matter: a credit_ledger row carrying the actor and the reason (the money, and what keeps balance == SUM(delta) true) and an admin_audit_log row (the admin action). Plan §13.1 scenario 12 asserts both — a mutating admin call with no audit row is a finding regardless of whether the ledger is right.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
deltanumber | stringyesSigned movement in credits. Negative debits the org.
reasonstringyesWhy this adjustment was made. Mandatory: it lands on both the ledger row and the audit row, and an unexplained manual movement is indistinguishable from a bug later.

Responses

StatusDescriptionBody
200Successful ResponseAdjustCreditsResponse
422Validation ErrorHTTPValidationError

GET /api/admin/orgs/{org_id}/credits/forensics

Section titled “GET /api/admin/orgs/{org_id}/credits/forensics”

Org Credit Forensics

Burn drill-down for one org, plus the reconciliation check.

The buckets are windowed; ledger_total and reconciled are not. That split is deliberate — a drill-down answers “where did this month go”, while reconciliation is an all-time property (balance + purchased_balance must equal the sum of every row), and computing it over a window would make it pass by accident. Plan §13.1 scenario 12 asserts the view sums to the ledger.

Every bucket carries dollars alongside credits, read from the burn receipts rather than divided back out of the credits. The two are not the same question — credits are what the org was charged, dollars are what the usage cost us — and having both on one row is what makes a multiplier’s effect visible without opening the model catalog.

Parameters

NameInTypeRequiredDescription
daysqueryintegernoWindow size, ending now.
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseForensicsResponse
422Validation ErrorHTTPValidationError

POST /api/admin/orgs/{org_id}/reset-baseline

Section titled “POST /api/admin/orgs/{org_id}/reset-baseline”

Reset Org Baseline

Put an org back on a known plan, balance, floor, overrides and period.

Orgs cannot be deleted, so this is teardown for the credits e2e sweep’s persistent fixture orgs (plan §13.1). Without it they drift and a later run passes for the wrong reason — a scenario asserting “the gate refuses” trivially succeeds against an org somebody left at zero.

The balance lands via an adjustment, never an UPDATE credit_balances: a hand-written balance write leaves balance != SUM(credit_ledger.delta) and silently disables the reconciliation check every later run depends on.

Idempotent in the sense that matters — the second call finds the org already at baseline, computes a zero delta and writes no ledger row. It is not “no writes at all”: the ledger is append-only, so a reset that does move something adds to it rather than erasing history.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
plan_codestringyes
reasonstringno

Responses

StatusDescriptionBody
200Successful ResponseResetBaselineResponse
422Validation ErrorHTTPValidationError

Roll Org Period

Run the period close for one org, optionally forcing the boundary.

Two very different operations behind one endpoint:

  • Un-forced is just running the scheduled job early, narrowed to this org. If the period has not ended the org is not due and nothing happens, so it is safe anywhere and needs no gate.
  • Forced pulls period_end back to now first, which expires a live balance and issues the next grant ahead of schedule. That is real money moving on a schedule nobody agreed to, so it is refused unless CREDITS_FORCE_PERIOD_ROLL_ENABLED is set — non-prod only, and default-deny so a fresh deployment is production-shaped. Staging turns it on for plan §13.1 scenarios 5 and 8, which cannot otherwise observe a rollover without waiting a month.

The close itself is the period job, unchanged — it opens its own session per org and commits there, so this handler commits its own work first.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
forcebooleannoPull period_end back to now first, so the close runs even though the period has not ended. Non-prod only — this expires a live balance and grants early.

Responses

StatusDescriptionBody
200Successful ResponseRollPeriodResponse
422Validation ErrorHTTPValidationError

Update Org Subscription

Change an org’s plan, its entitlement overrides, or its period boundary.

A plan change goes through change_plan, which expires the outgoing balance and grants the new plan’s allocation — once ever per org per plan, so an org returning to a plan it has already had gets nothing and must be topped up with an adjustment instead.

Overrides are tri-state on the wire: a field omitted from overrides is left as it is, an explicit null clears it back to inheriting the plan, and a value sets it. Sending overrides: {} therefore changes nothing — clearing them all means naming them all, or using reset-baseline.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
override_reasonstringno
overridesEntitlementOverridesno
period_endstring (date-time)no
plan_codestringno
statusstringno

Responses

StatusDescriptionBody
200Successful ResponseOrgCreditsResponse
422Validation ErrorHTTPValidationError

List Plans

Every plan, with its price point, margin and subscriber count.

The fields list is the entitlement set, derived from ENTITLEMENT_FIELDS. The editor renders from it rather than from a hand-written list, so a new entitlement cannot exist on the server and be missing from the screen that is supposed to set it.

Responses

StatusDescriptionBody
200Successful ResponsePlanListResponse
422Validation ErrorHTTPValidationError

Create Plan

Create a plan. code must be unique.

Request body (required)

FieldTypeRequiredDescription
audit_logbooleanno
byo_keysbooleanno
codestringyes
direct_model_choicebooleanno
enforced_idp_ssobooleanno
enforces_creditsbooleanno
hitl_routingbooleanno
initial_creditsnumber | stringno
intake_classifier_calls_per_dayintegerno
intake_messages_per_dayintegerno
is_enterprisebooleanno
max_agentsintegerno
max_concurrent_chatsintegerno
max_custom_environmentsintegerno
max_custom_image_gbintegerno
max_custom_modelsintegerno
max_inboxesintegerno
max_integrationsintegerno
max_queue_depthintegerno
max_seatsintegerno
monthly_creditsnumber | stringno
namestringyes
overdraft_pctnumber | stringno
per_agent_budgetsbooleanno
price_currencystringnoISO 4217 alpha-3 the advertised price is quoted in. Set it to whatever the plan’s Polar product is priced in.
price_displaynumber | stringno
renewsbooleanno
shared_blocksbooleanno
sort_orderintegerno

Responses

StatusDescriptionBody
201Successful ResponsePlanResponse
422Validation ErrorHTTPValidationError

Update Plan

Replace a plan’s figures and entitlements.

Takes effect on the next entitlement read: EntitlementService caches per instance and an instance lives for one request, so no running request is retroactively re-permissioned and the one after this sees the new values.

Parameters

NameInTypeRequiredDescription
plan_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
audit_logbooleanno
byo_keysbooleanno
codestringyes
direct_model_choicebooleanno
enforced_idp_ssobooleanno
enforces_creditsbooleanno
hitl_routingbooleanno
initial_creditsnumber | stringno
intake_classifier_calls_per_dayintegerno
intake_messages_per_dayintegerno
is_enterprisebooleanno
max_agentsintegerno
max_concurrent_chatsintegerno
max_custom_environmentsintegerno
max_custom_image_gbintegerno
max_custom_modelsintegerno
max_inboxesintegerno
max_integrationsintegerno
max_queue_depthintegerno
max_seatsintegerno
monthly_creditsnumber | stringno
namestringyes
overdraft_pctnumber | stringno
per_agent_budgetsbooleanno
price_currencystringnoISO 4217 alpha-3 the advertised price is quoted in. Set it to whatever the plan’s Polar product is priced in.
price_displaynumber | stringno
renewsbooleanno
shared_blocksbooleanno
sort_orderintegerno

Responses

StatusDescriptionBody
200Successful ResponsePlanResponse
422Validation ErrorHTTPValidationError

Delete Plan

Delete a plan. Refused while any org is subscribed to it.

409 rather than a cascade: the subscription’s plan_id is what every entitlement read resolves through, so removing the plan under a live subscriber turns that org UNSUBSCRIBED — everything denied — from a click that looked like tidying up.

Parameters

NameInTypeRequiredDescription
plan_idpathstring (uuid)yes

Responses

StatusDescriptionBody
204Successful Response
422Validation ErrorHTTPValidationError