Admin Billing Config
The billing admin config panel (/admin/tab/billing) lets super-admins manage all platform billing settings through the UI — no SQL or migrations required.
Access Control
Section titled “Access Control”All endpoints require is_super_admin. Non-super-admin requests receive 403 Forbidden.
Panel Layout
Section titled “Panel Layout”The tab renders five cards:
- Stripe & System Defaults — Stripe API keys and overdraft limit
- Credit Conversion Rates — cost in credits for every billable operation
- Subscription Tiers — edit the three seeded tiers (no create/delete)
- Credit Packs — full CRUD for purchasable credit packs
- Billing Config History — last 12 audit entries
Database Tables
Section titled “Database Tables”billing_config (singleton)
Section titled “billing_config (singleton)”Stores the 14 credit conversion rates. Seeded with one row on first migration — always queried with LIMIT 1.
| Column | Type | Description |
|---|---|---|
credit_price_cents | i32 | Price per credit in USD cents |
voice_call_credits | i32 | Credits per ceil-minute of voice call |
voice_recording_credits | i32 | Credits per ceil-minute of recording |
sms_outbound_credits | i32 | Credits per outbound SMS segment |
sms_inbound_credits | i32 | Credits per inbound SMS segment |
mms_outbound_credits | i32 | Credits per outbound MMS |
ai_budget_1k_credits | i32 | Credits per 1K tokens (budget tier) |
ai_mid_1k_credits | i32 | Credits per 1K tokens (mid tier) |
ai_premium_1k_credits | i32 | Credits per 1K tokens (premium tier) |
ai_ultra_1k_credits | i32 | Credits per 1K tokens (ultra tier) |
transcription_credits | i32 | Credits per ceil-minute of transcription |
realtime_openai_credits | i32 | Credits per minute of OpenAI realtime |
realtime_gemini_credits | i32 | Credits per minute of Gemini realtime |
email_outbound_credits | i32 | Credits per outbound email |
subscription_tier
Section titled “subscription_tier”Three seeded tiers: Starter, Pro, Enterprise. Ordered by sort_order.
Key fields: name, slug, stripe_price_id, price_per_seat_cents, credits_per_seat, 13 *_included allowance fields (Option<i32>), feature gates (autonomous_plans_enabled, knowledge_base_enabled, custom_analyzers_enabled), call_analysis_level, is_active.
The *_included fields use tri-state encoding:
None— operation not available on this tierSome(0)— available but zero included (overage-only)Some(n)— n credits included per billing cycle
credit_pack
Section titled “credit_pack”Fields: name, credits, price_cents, stripe_price_id, is_active, sort_order. No seed data — operators create packs through the UI.
core_conf additions
Section titled “core_conf additions”Two new Stripe fields (stripe_secret_key, stripe_webhook_secret) and overdraft_limit_credits (default 0 = no overdraft).
API Endpoints
Section titled “API Endpoints”Billing config
Section titled “Billing config”| Method | Path | Description |
|---|---|---|
GET | /api/admin/billing-config | Load config with masked Stripe secrets |
PUT | /api/admin/billing-config | Update Stripe keys, overdraft, and all rates atomically |
Subscription tiers
Section titled “Subscription tiers”| Method | Path | Description |
|---|---|---|
GET | /api/admin/subscription-tiers | List all tiers by sort order |
PUT | /api/admin/subscription-tiers/{id} | Update a single tier |
Credit packs
Section titled “Credit packs”| Method | Path | Description |
|---|---|---|
GET | /api/admin/credit-packs | List all packs by sort order |
POST | /api/admin/credit-packs | Create a new pack |
PUT | /api/admin/credit-packs/{id} | Update a pack |
DELETE | /api/admin/credit-packs/{id}/archive | Soft-archive a pack (is_active = false) |
Secret Masking
Section titled “Secret Masking”Stripe keys follow a round-trip masking protocol:
- GET returns
"__stored__"when a secret exists,nullwhen unset - PUT interprets
"__stored__"as “keep current value” — no change - PUT interprets an empty string as “clear this field”
- PUT validates new values by prefix:
sk_live_,sk_test_, orrk_for the secret key;whsec_for the webhook secret
Sensitive values never appear in API responses or audit logs.
Atomic Transactions
Section titled “Atomic Transactions”The PUT /api/admin/billing-config handler wraps Stripe keys, overdraft, and all 14 rates in a single SeaORM transaction. If any update fails, everything rolls back.
db.transaction::<_, (), AppError>(|txn| { Box::pin(async move { core_active.update(txn).await?; billing_active.update(txn).await?; Ok(()) })}).await?;Audit Trail
Section titled “Audit Trail”Every mutation records an entry in admin_config_audit with scope "billing":
| Field | Example |
|---|---|
action | config.billing.update, config.tier.update, config.credit_pack.create |
changed_fields | ["overdraft_limit_credits", "voice_call_credits"] |
status | "saved" or "failed" |
title | "Updated billing configuration" |
Only field names are logged — never values. The history card displays the last 12 entries.
Environment Variables
Section titled “Environment Variables”| Variable | Required | Description |
|---|---|---|
STRIPE_SECRET_KEY | No | Seeded into core_conf on first migration |
STRIPE_WEBHOOK_SECRET | No | Seeded into core_conf on first migration |
Both are optional so dev environments without Stripe can run migrations. Once set through the admin UI, the database values take precedence.
Key Files
Section titled “Key Files”| Layer | Path |
|---|---|
| Types | src/mods/admin/types/admin_billing_config_types.rs |
| Handlers | src/mods/admin/api/get_admin_billing_config_api.rs, update_admin_billing_config_api.rs |
| Services | src/mods/admin/services/admin_billing_config_service.rs |
| Tier service | src/mods/admin/services/admin_subscription_tier_service.rs |
| Pack service | src/mods/admin/services/admin_credit_pack_service.rs |
| UI | src/mods/admin/components/admin_billing_config_tab_component.rs |
| Migrations | migration/src/m20260415_*.rs, m20260421_*.rs |