Credit system architecture
Sophia uses a prepaid credit ledger per organization: lots (pools with expiry), an append-only ledger, and cached available / reserved balances on the organization. Money movement runs in MongoDB transactions across all three.
flowchart LR Purchase["Purchase<br/>Stripe / trial / manual"] --> Lot["Credit lot<br/>remaining pool"] Lot --> Reserve["Reserve<br/>schedule interview"] Reserve --> Settle["Settle<br/>post-interview"] Settle --> Release["Release<br/>unused hold"] Settle --> Expiry["Expiry<br/>forfeited credits"] Release --> Lot
Rules
- 1 credit = 1 interview minute by default (
creditsPerInterviewMinute, usually1). - Quote at schedule: cost =
ceil(durationMinutes × rate); scheduling fails if available balance is insufficient. - Reserve moves credits from available → reserved (earliest lot expiry first); interview stores the reserved amount.
- Charge actual usage, not the full reserve: after completion, consumed =
min(floor(durationSeconds / 60), reserved); the rest is released. - Failed interviews settle with zero consumption; full reserved amount is released.
- Settlement is async (SQS after terminal interview); scheduling talks to credits only through the credit port, not direct ledger access.
See Credit flow diagram and Stripe webhooks.