Wallet (Telephony Balance)
Audience: Engineering · Where in app: sidebar wallet pill; Packages → Wallet · Since: 2026-07
The "wallet" is not a new balance — it reuses SubscriptionModel.remaining_purchased_ai_credits (purchased AI-credit add-ons, 1 credit = $0.01). It funds telephony (number purchase, monthly rental, per-call Twilio cost) and doubles as the overflow pool for AI usage.
Semantics
| Balance | Purpose | Cycle behavior |
|---|---|---|
remaining_ai_credits | Plan credits — AI-only, never touched by telephony | Reset to the plan allotment on renewal |
remaining_purchased_ai_credits | The wallet — telephony and AI fallback | Carries over — renewal jobs reset plan credits only |
- AI draw-down (
AITokensCreditJob) spends plan credits first, then falls into the wallet. - Telephony debits come only from the wallet. The balance may go negative (burst / mid-call) — carried forward and paid down by the next top-up.
Gates
GET /internal/telephony/wallet/:company_id returns both flags:
has_telephony: wallet > 0— premium gate for buy/rental/calls.ai_available: plan > 0 || wallet > 0— AI falls back to the wallet.- Plus
markup_percent(see below) so ms-ai gates number purchase on the marked-up price and the frontend shows final prices.
Backend endpoints
backend/app/controllers/TelephonyWalletController.js, routes in
backend/app/routes/internalTelephonyRoutes.js, ms-ai → backend auth via apiKeyAuth (BACKEND_API_KEY):
| Route | What |
|---|---|
GET /internal/telephony/wallet/:company_id | Balance + gate flags + markup_percent |
POST /internal/telephony/debit | Idempotent debit: reserves idempotency_key (unique index on ai_credit_logs) before the $inc, so the 10-min call sweep / rental cron never double-charge |
POST /internal/telephony/notify | Rental suspend_warning (day 0/3/6) / released emails — backend owns templates + billing-user resolution |
Markup. credits in the debit body is the raw Twilio cost; the wallet is charged base × (1 + markup% / 100) with sub-cent precision (deliberately not rounded to a whole cent). Markup comes from the settings key telephony_markup_percent (default 0); margin_credits is recorded on the ledger row for revenue reporting.
Billing-user resolution (_resolveBillingUser): sub-account owner if they have their own card → agency owner → subscription owner. Notify emails are best-effort — a mail failure never fails the request.
Crons
| Cron | Where / schedule | What |
|---|---|---|
number-rental:meter-due | ms-ai src/elevenlabs/number-rental.scheduler.ts, daily 06:00 UTC | Monthly number rental; idempotent on (number, period), catches up missed days |
twilio-call-cost:meter | ms-ai, every 10 min | Sweeps each company subaccount's completed calls; idempotent on CallSid |
number-rental:suspension-sweep | ms-ai, every 15 min | Suspend → warn (day 0/3/6 via /notify) → release after 7 days |
BalanceAutoRechargeJob | backend scheduler, every minute | Tops up the wallet from the saved card when below balance_auto_recharge.threshold_credits |
Kill switch for all ms-ai rental crons: NUMBER_RENTAL_CRON_ENABLED=false. Bring-your-own Twilio numbers are never metered. The suspension sweep intentionally runs slower than auto-recharge so a healthy card tops up before anything suspends.
Telephony charges are recorded in ms-ai cost-tracking via recordFlatUsdCharge (same mechanism as Agent K provider usage, separate providers for reconciliation) — see cost-tracking.
Auto-recharge
SubscriptionModel.balance_auto_recharge subdoc: { enabled, prompted, threshold_credits, addon_id }, managed via SubscriptionController (enabling requires an addon selected). Purchases with no card on file return a clear 402 (payment error, not a 500).
Frontend
- Sidebar pill —
src/components/company/CompanyNavigationSidebar.vue(walletUsd, amber when low). - Wallet page —
src/views/packages/Wallet.vue,GET /subscription/wallet-transactions. Type badges:buy_addon(Top-up),balance_auto_recharge(Auto-recharge),telephony_rental(Number rental),telephony_call(Call),telephony_debit(Telephony). - Tabs —
src/components/packages/PackagesTabs.vue: Wallet / Add-ons / Payment Methods. - Access —
src/composables/useBalanceAccess.jsnow grantscanUseTelephonyandcanSelfServeTopupto everyone, including agency sub-accounts (full self-service; the old agency-funded restriction is lifted).isAgencySubAccountremains only for copy/tab tailoring — subscription & invoices stay agency-owned.
Related
- AI Credits — draw-down order, audit log
- Plans & Subscriptions
- AI Cost-Tracking (Admin) — where telephony/provider charges land