Skip to content

Call Fee Model Platform Fee (BYOK) & P2P Fee ​

Oktivo platform documentation for developers. Covers how each type of call is priced: normal preset rate, the platform fee (for BYOK / custom-preset and browser calls), and the P2P fee (for phone-to-phone calls).


Overview ​

Not every call is billed against a voice preset. Some call types have no AI agent (or no platform-provided STT/TTS/LLM), so charging a preset rate would be wrong. For those, we bill a flat fee per billing unit instead.

There are two such fees, and they behave identically except for what triggers them:

FeeTriggers when…Why
Platform feeThe call uses a custom preset (BYOK) the customer supplies their own STT/TTS/LLM keys OR the call is a browser-to-phone (human) call.The platform pays no provider costs, so only a flat platform/orchestration fee applies.
P2P feeThe call is phone-to-phone bridging two phone numbers, no AI agent.No preset applies to a pure phone bridge.

Both fees follow the same tiered floor model as preset rates (global → per-org → org → per-customer), and add-ons (recording / transcription / analysis) are still charged on top of either fee.


Which call type is billed how ​

Call typecallTypeBilled atSnapshot flag
AI agent, normal presetai_agentPreset rate (cost_per_min / interval pricing → org rate → customer rate)
AI agent, custom preset (BYOK)ai_agent + byokConfig.customPresetEnabled (or preset_id === "custom")Platform feeis_platform_fee: true
Browser-to-phone (human)humanPlatform feeis_platform_fee: true
Phone-to-phone (P2P)phone_to_phoneP2P feeis_p2p_fee: true

A call is never billed at more than one fee. The resolver checks P2P first, then custom-preset/platform, then falls back to the normal preset rate.


The 4-tier floor model (same for both fees) ​

SuperAdmin global fee (floor)  ──►  Per-org superadmin fee (optional)  ──►  Org fee (margin)  ──►  Per-customer override (optional)
  • SuperAdmin sets a global fee the floor. 0 = free at the platform level.
  • SuperAdmin (optional) can set a preferential per-org fee for a specific org. When set, it becomes both the wholesale cost charged to that org and the floor the org must stay at or above.
  • Org may set its own fee ≥ the effective floor to add margin. If unset, the org uses the floor.
  • Org (optional) may set a per-customer override ≥ the org fee to charge a specific customer a different rate. If unset, the customer uses the org fee.
  • Customer is billed at the most specific rate set (customer override if present, else org fee). The wholesale cost stays at the effective superadmin fee, so the org keeps (customer_rate − superadmin_fee) × units as margin — exactly like preset margin.

This mirrors the add-on rate tiers (CustomerAddonPricing). The per-customer fee override is stored in the CustomerFeePricing collection (one doc per customer per org, with platform_fee_per_min/sec and p2p_fee_per_min/sec).

Both fees support per-minute and per-second rates. For sub-minute billing units, the per-second fee is scaled by the interval (and if per_sec is unset it is derived from per_min / 60).

Floor enforcement & cascade ​

  • An org fee below the effective floor is rejected (400) when set via the API, and defensively raised to the floor at billing time.
  • When a superadmin sets a per-org fee above the org's current fee, the org's fee is auto-raised to the new floor.
  • A per-customer override below the org fee is rejected (400) when set, and defensively ignored (floored to the org fee) at billing time a customer is never billed below what the org itself pays.

Data model ​

Global fees SystemConfig ​

typescript
interface IPlatformFee { per_min: number; per_sec?: number }  // BYOK / browser
interface IP2pFee      { per_min: number; per_sec?: number }  // phone-to-phone

interface ISystemConfig {
  // ...
  platform_fee: IPlatformFee;  // default { per_min: 0 }
  p2p_fee: IP2pFee;            // default { per_min: 0 }
}

per_min allows 2 decimal places; per_sec allows up to 6.

Per-org fees OrgConfigReplica ​

typescript
interface IOrgPlatformFee {
  org_rate?: number | null;                    // org margin (per minute)
  org_rate_per_sec?: number | null;            // org margin (per second)
  superadmin_org_rate?: number | null;         // per-org superadmin floor (per minute)
  superadmin_org_rate_per_sec?: number | null; // per-org superadmin floor (per second)
}
interface IOrgP2pFee { /* identical shape */ }

interface IOrgConfigReplica {
  // ...
  platform_fee?: IOrgPlatformFee;
  p2p_fee?: IOrgP2pFee;
}

Per-customer fees CustomerFeePricing ​

One document per customer per org. Each value must be ≥ the org fee; null means "use the org fee". Preset-independent (fees don't vary by preset).

typescript
interface ICustomerFeePricing {
  orgUID: string;
  customerUID: string;
  platform_fee_per_min?: number | null;
  platform_fee_per_sec?: number | null;
  p2p_fee_per_min?: number | null;
  p2p_fee_per_sec?: number | null;
}
// unique index: { orgUID, customerUID }

Billing flow ​

1. Rate snapshot (call end) ​

captureRateSnapshot(orgUID, presetId, customerUID, transcriptionMode, isCustomPreset, isP2p) in utils/rateResolver.ts decides the path:

  • isP2p === true → captureP2pFeeSnapshot(...) → snapshot marked is_p2p_fee: true.
  • else isCustomPreset === true || presetId === "custom" → capturePlatformFeeSnapshot(...) → is_platform_fee: true.
  • else → normal preset resolution (interval pricing / per-minute / per-second).

On a fee path with a customerUID, the resolver loads the customer's CustomerFeePricing doc and passes the relevant override into the snapshot function, which applies it (floored at the org fee) via applyCustomerFeeOverride. When applied, rate_source becomes "customer_override".

Add-ons are resolved and attached to every snapshot regardless of path.

2. Consumption (call end) ​

CreditService.consumeCredits(...) (services/credit.service.ts) uses the snapshot's org_rate (customer-facing) and superadmin_rate (wholesale), adds enabled add-on rates on top, debits only the customer wallet, and records the org's wholesale cost + margin as reporting counters (org wallet untouched).

The transaction metadata carries is_platform_fee and is_p2p_fee so the UI can label the charge.

Where each fee is triggered in code ​

Call typeFileCall
AI custom preset (BYOK)controllers/webhook.controller.tscaptureRateSnapshot(..., isCustomPreset=true) when byokConfig.customPresetEnabled / preset_id === "custom"
Browser (human)services/humanCall.service.tscaptureRateSnapshot(..., "recording", true)
Phone-to-phone (P2P)services/phoneToPhoneCall.service.tscaptureRateSnapshot(..., "recording", false, true)

For browser and P2P calls the transcript comes from Sarvam STT on the recording, so transcription (if the customer has it) is billed at the recording rate (call_transcription_stt).


API endpoints ​

SuperAdmin global fees (the floor) ​

MethodPathBody
PUT/superadmin/system-config/platform-fee{ per_min?, per_sec? } (per_sec: null clears)
PUT/superadmin/system-config/p2p-fee{ per_min?, per_sec? }

SuperAdmin per-org fees ​

MethodPathBody
GET/api/org-config/replica/:orgUID/platform-fee
PUT/api/org-config/replica/:orgUID/platform-fee{ superadmin_org_rate?, superadmin_org_rate_per_sec? }
GET/api/org-config/replica/:orgUID/p2p-fee
PUT/api/org-config/replica/:orgUID/p2p-fee{ superadmin_org_rate?, superadmin_org_rate_per_sec? }

Org admin (edit) + Customer (read-only) ​

MethodPathRoleWhat it does
GET/api/org-config/replica/platform-feeadmin, customerEffective platform fee (floor + org rate)
PUT/api/org-config/replica/platform-feeadminSet org platform fee (≥ floor)
GET/api/org-config/replica/p2p-feeadmin, customerEffective P2P fee
PUT/api/org-config/replica/p2p-feeadminSet org P2P fee (≥ floor)

Org admin per-customer fee overrides ​

MethodPathRoleWhat it does
GET/api/org-config/replica/fees/customersadminList all per-customer fee overrides in the org
GET/api/org-config/replica/fees/customers/:customerUIDadmin, customerEffective per-customer fee view (org floor + override)
PUT/api/org-config/replica/fees/customers/:customerUIDadminUpsert customer fee overrides (each ≥ org fee; null clears)
DELETE/api/org-config/replica/fees/customers/:customerUIDadminRemove customer overrides (revert to org fees)

Frontend ​

RoleWhereComponent
SuperAdminSystem Config panelGlobal "Platform Fee" and "P2P Fee" editors in SystemConfigPanel.tsx
SuperAdminPer-Org Rate Management (/superadmin/org-rates)SuperadminOrgFeeEditor.tsx (per-org platform fee + P2P fee)
Org AdminConfig Management → PricingPlatformFeeEditor.tsx, P2pFeeEditor.tsx (editable)
Org AdminConfig Management → Customer OverridesCustomerFeeOverrides.tsx (per-customer platform + P2P fees)
CustomerAdd Credits / Credits pagePlatformFeeEditor readOnly, P2pFeeEditor readOnly
Customer (CRM app)Credits pageTransaction badges: Platform fee (BYOK) and P2P fee

Backend code map ​

client_backend/src/
├── models/
│   ├── SystemConfig.ts          # IPlatformFee, IP2pFee (global floors)
│   ├── OrgConfigReplica.ts      # IOrgPlatformFee, IOrgP2pFee (per-org)
│   └── CustomerFeePricing.ts    # per-customer platform/P2P fee overrides
├── repositories/
│   └── orgConfigReplica.repository.ts  # setPlatformFee, setP2pFee
├── services/
│   ├── config.service.ts        # updatePlatformFee, updateP2pFee (global)
│   ├── orgConfigReplica.service.ts     # get/update + superadmin get/set + per-customer get/list/upsert/delete (floor-enforced)
│   ├── credit.service.ts        # consumeCredits persists is_platform_fee / is_p2p_fee
│   ├── humanCall.service.ts     # browser calls → platform fee
│   └── phoneToPhoneCall.service.ts     # P2P calls → P2P fee
├── utils/
│   └── rateResolver.ts          # capturePlatformFeeSnapshot, captureP2pFeeSnapshot, applyCustomerFeeOverride, routing
├── controllers/
│   ├── systemConfig.controller.ts      # updateDefaultPlatformFee, updateDefaultP2pFee
│   ├── orgConfigReplica.controller.ts  # get/update/superadmin + per-customer fee handlers
│   └── webhook.controller.ts    # AI custom-preset → platform fee
└── routes/
    ├── superadmin.routes.ts     # global fee routes
    └── orgConfigReplica.routes.ts       # org + per-org + per-customer fee routes

Setup & rollout ​

  • Both fees default to 0. Until a superadmin sets them, BYOK / browser / P2P calls are billed free (only add-ons, if any, apply). Set them on the superadmin System Config page.
  • Backward compatible. Existing config documents without platform_fee / p2p_fee are treated as 0. Existing preset-based billing is unchanged.
  • CRM_HUMAN_CALL_PRESET_ID now only affects audit metadata on human/P2P transactions (it no longer drives the rate).

Tests ​

  • platform-fee-rate-resolution.test.ts, platform-fee-floor-enforcement.test.ts, platform-fee-billing.integration.test.ts
  • p2p-fee-rate-resolution.test.ts, p2p-fee-billing.integration.test.ts
  • human-call-platform-fee.test.ts
  • customer-fee-override.test.ts (per-customer override tier)

Run all fee tests:

bash
cd backend/client_backend
npx vitest --run src/__tests__/platform-fee-*.test.ts src/__tests__/p2p-fee-*.test.ts src/__tests__/human-call-platform-fee.test.ts src/__tests__/customer-fee-override.test.ts

Last updated: