Overview
Allternit Cloud Voice runs on Allternit servers. The app asks the cloud API for a short-lived signed ticket, then opens the voice session WebSocket with it. The voice service redeems the ticket (single use) and reports the session’s seconds when it ends. The cloud API only meters and exposes usage: it does not charge anyone or write to Stripe. For the voice service itself (TTS, STT, sessions), see Voice API.Base URL
Pricing constants
Defined once incmd/allternit-cloud-api/src/services/voice_usage.rs and reused by the phone path.
The plan comes from the account’s active subscription (the same lookup as
GET /api/v1/me/usage). Plus, Super and Ultra include 100 Cloud Voice minutes a month and can continue into overage. Free includes none and cannot use Cloud Voice.
Endpoints
Mint a ticket
POST /api/v1/voice/tickets — Clerk session.
Returns 200:
payload is base64url JSON {sub, plan, exp, nonce, maxSeconds} (exp in Unix seconds, maxSeconds 1800), and signature is base64url HMAC-SHA256 of "v1." + payload with ALLTERNIT_VOICE_TICKET_SECRET.
Redeem a ticket
POST /api/v1/voice/tickets/redeem — voice service only, Authorization: Bearer <ALLTERNIT_VOICE_WORKER_TOKEN>.
Body {"ticket": "..."}. Verifies the signature and expiry, then records the nonce so the ticket works once. Returns {"sub", "plan", "maxSeconds"}. A bad, expired or already-used ticket returns 401 (invalid-ticket or ticket-used). A wrong bearer token returns 401; an unconfigured token or secret returns 503 cloud-unavailable. The voice service can also verify the signature locally; this endpoint adds the single-use check.
Report usage
POST /api/v1/voice/usage — voice service only, same bearer token.
(engine, sessionId): retrying a report does not double count. engine must be cloud; seconds is 0 to 21600.
This month’s usage
GET /api/v1/voice/usage/month — Clerk session.
estimatedOverageCents is the running estimate of this month’s voice charges; billingEnabled is true only when overage billing is live (ALLTERNIT_VOICE_BILLING_MODE=live); while it is off or in dry_run (charges computed, never sent to Stripe) it is false, so clients should hide the estimate rather than promise a charge.
Overage billing (admin-approved)
Per user and UTC month: cloud minutes over the plan allowance (100 on Plus, none on free) at 0.08, and $2 per phone number held during the month. Each engine’s seconds are rounded up to whole minutes. Results are stored invoice_billing_periods (state pending, dry_run, invoiced or failed), one row per user and period, so re-running a period never bills twice.
ALLTERNIT_VOICE_BILLING_MODE is off (default, does nothing), dry_run (computes rows and stores the exact Stripe request bodies, no Stripe calls) or live. In live mode nothing is sent until an admin approves that period. The sent charges are Stripe invoice items attached to the user’s subscription, with idempotency keys, so they land on the next subscription invoice. Price ids come from STRIPE_PRICE_VOICE_MINUTE and STRIPE_PRICE_PHONE_NUMBER.
Environment variables
Database
Migration029_voice_usage.sql adds voice_usage (one row per session or call, unique on (engine, ref), indexed on (user_id, occurred_at)) and voice_tickets_used (redeemed nonces; expired rows are purged on redeem). Migration 031_voice_billing_periods.sql adds voice_billing_periods. Prod migrations are applied by hand.