Skip to main content
Restricted Access: This documentation is only accessible to @tenzo.ai email addresses.

Overview

The billing system tracks credit usage across calls and SMS messages. Each organization has a billing model that defines how credits are consumed. Credits are added manually by support and deducted automatically when calls complete or SMS messages are sent/received. Credits remaining = SUM(billing_credit_additions.credits_added) - SUM(billing_credit_usage.credits_used)

Database Tables

Three tables power the billing system:

billing_credit_usage Columns

Usage Types (BillingUsageType enum)

Billing Models

Each organization is assigned one billing model via org_billing_settings. The model determines how credits are calculated for calls and SMS.

PER_INTERVIEW

Flat 1 credit per qualifying interview. No SMS billing.

INTERVIEW_LENGTH

Length-based flat rate per qualifying interview. No SMS billing.

PER_CREDIT

Per-minute call billing + per-segment SMS billing. Calls: No completion rate gate — voicemails and no-answers with duration still cost credits. SMS:

LUXUS

Multi-event billing with separate charges for attempt, minutes, and answered bonus. Calls: A single answered call can produce up to 3 billing events. An unanswered call (voicemail, no-answer) produces only the attempt event. SMS:

PER_PLACEMENT

No automatic billing. Credits are not deducted for calls or SMS.

Idempotency

Every billing event has a usage_key that is unique in the database. The DAO uses ON CONFLICT (usage_key) DO NOTHING when inserting, so retries and concurrent writes cannot double-charge.

Key Patterns

Integration Points

Billing events are generated at three points in the application:

Call Billing

CallPostProcessor._record_billing_credit_usage() (call_post_processor.py:2163) fires after every completed call:
  1. Loads the call with its campaign relationship to get org_id
  2. Fetches org_billing_settings for the org
  3. Calls generate_call_billing_events(call, billing_model) — pure function, no I/O
  4. Passes the resulting events to billing_dao.record_billing_events()
  5. Errors are caught and logged — billing failures never crash the post-processing pipeline

Outbound SMS Billing

send_smses() (texting_helpers.py:417) fires after each Twilio message is sent:
  1. Fetches billing settings for the campaign’s org
  2. Calls generate_sms_billing_event(message_length, "outbound", sent_message.sid, billing_model)
  3. If the model charges for SMS, records the event via billing_dao.record_sms_billing_event()

Inbound SMS Billing

failed_call_sms() (text_webhooks.py:660) fires on incoming SMS webhooks:
  1. Resolves the campaign and org from the most recent call for the sender
  2. Fetches billing settings for the org
  3. Calls generate_sms_billing_event(message_length, "inbound", message_sid, billing_model)
  4. If the model charges for SMS, records the event via billing_dao.record_sms_billing_event()

Architecture

The billing system follows the project’s separation of concerns:

Client Billing Visibility

The billing page is gated behind the CLIENT_BILLING_PAGE_VISIBLE feature flag (per-org, default off). When enabled, non-support users see a read-only billing page with credits remaining, usage history, and addition history. Support-only controls (billing model selector, add credits, backfill, placements tab) remain hidden.

Enabling for an Org

No admin UI — update directly in the org_feature_flags table:
Or via DAO: feature_flags_dao.set_flag(org_id, FeatureFlag.CLIENT_BILLING_PAGE_VISIBLE, True)

Backfill Script

server/scripts/backfill_billing_event_usage.py normalizes existing data for the event model migration. It is idempotent and safe to run multiple times. Usage:
Steps:
  1. Rename PER_CREDIT to INTERVIEW_LENGTH in org_billing_settings.billing_model
  2. Rename PER_CREDIT to INTERVIEW_LENGTH in billing_credit_usage.billing_model
  3. Backfill usage_key from call_id_str where missing (usage_key = 'call:' || call_id_str)
  4. Normalize NULL usage_type to CALL_FLAT
  5. Validation check — reports any rows still missing usage_key

API Endpoints

Support Endpoints (/support/billing/{org_id}/...)

Client Endpoints (/billing/...)

All client endpoints require CLIENT_BILLING_PAGE_VISIBLE flag to be enabled. Auth context provides org_id.

File Index