# Building Voice Agent Platforms

> Host many customers' voice agents on one platform: per-tenant provisioning, scoped API keys, data isolation, per-tenant agent config, and usage metering.

You are building a platform. Your customers sign up, configure their own voice
agents, plug in their own numbers and provider keys, and pay you for what they
use. All of it runs on top of FirstByte Voice. This page shows how a tenant
is isolated end to end, what each tenant gets at provisioning, and where the
billing meters sit.

The whole model hangs off one stable identifier: the **org id**. Every tenant
resource (DNS label, SIP realm, data rows, sealed credentials, usage account)
is keyed on the org id, never on a human-facing slug. Thus a customer can
rename their workspace without a move of any infrastructure.

> **NOTE:**
> Org onboarding (the console) triggers provisioning. Your platform backend can
> also trigger it through the control-plane API. Each step below is idempotent
> and best-effort. Onboarding completes even if one subsystem is briefly
> unavailable. A re-run of the bootstrap backfills whatever was missed.

## What a tenant gets

  - **Per-tenant SIP + WebRTC endpoints** — `<org-id>.sip.firstbyte.fast` and `<org-id>.webrtc.firstbyte.fast` resolve
    to the engine. The engine routes inbound SIP INVITEs by their leftmost DNS
    label.
  - **Isolated analytics** — A read-only analytics role scoped by a row policy on `tenant_id`. Thus a
    customer's reports and CDRs can never read across the tenant boundary.
  - **A usage account** — A metering account keyed `Tenant=<org-id>` and bound to your rating plan.
    Thus every rated call debits the correct balance from the first minute.
  - **Managed recording storage** — A per-tenant object-storage prefix (`<org-id>/`) + access keys. If you
    leave a customer's recording target empty, recordings land in managed
    storage.

Onboarding also mints an **entitlement** in the entitlements control plane
(tier `starter | growth | scale | enterprise`, with your configured tier as the
default). It also mints a per-tenant realtime channel key. Thus CTI events and
screen-pop have a channel from day one.

## Provision a tenant

  1. **Create the org and bootstrap resources**
Onboarding creates the org record. It then runs the provisioning steps in
    parallel: DNS labels, the isolated analytics role + row policy, the usage
    account, storage keys, the entitlement, and the realtime channel. All are
    keyed on the new org id.
  2. **Point trunks and numbers at the tenant realm**
Register the customer's SIP trunk against their realm. Map DIDs to it. The
    engine extracts `<org-id>` from the leftmost DNS label of each inbound
    INVITE as the `tenant_id`. Thus no slug-to-id translation is ever needed.
    See [SIP trunking](/modalities/voice/transport-telephony/sip-trunking) and
    [number provisioning](/modalities/voice/transport-telephony/number-provisioning).
  3. **Publish the tenant's agent config**
When a customer saves an agent, the platform hydrates its pipeline config
    to the engine, keyed per agent. Provider credentials resolve per tenant
    (below). See
    [runtime configuration](/modalities/voice/runtime/configuration).
  4. **Issue scoped API keys**
Mint a key per customer (or per customer-app). Then their backend can drive
    calls and stream audio, and it never sees another tenant's traffic.

## Scoped API keys

Programmatic access uses opaque **bearer tokens** carried as
`Authorization: Bearer <key>`. The server stores keys **hashed** (it shows the
raw value once, at creation). Each key carries a **scope set**. You can give a
key an expiry, and you can revoke it at any time. The token resolves to exactly
one org id. That org id becomes the tenant boundary for every request the key
makes.

- **`scopes`** (`string[]`) — What the key may do, as `product:action` strings — for example `voice:read`,   `voice:write`, or a wildcard `voice:*` / `*`. The server refuses a request   (`403`) if the key lacks the scope that the endpoint requires.
- **`expires_at`** (`timestamp | null`) — Optional hard expiry. Expired keys authenticate as invalid.
- **`revoked_at`** (`timestamp | null`) — Set this to revoke immediately. The audit trail is not deleted.

A tenant's backend instantiates the SDK with its own key. Nothing about other
tenants is reachable through it:

  
```ts
import { Voice } from "@clutchcall/agents";

// One key per customer. Scopes gate what it can do; the key's org id
// is the tenant boundary the control plane enforces on every call.
const voice = new Voice({ apiKey: process.env.CLUTCHCALL_API_KEY! });

const call = await voice.calls.originate({
  to: "+15551234567",
  from: "+15557654321",
  app: "AI_BIDIRECTIONAL_STREAM",
});
```
  
  
```python
from clutchcall.voice import Voice
import os

voice = Voice(api_key=os.environ["CLUTCHCALL_API_KEY"])

call = voice.calls.originate(
    to="+15551234567",
    from_="+15557654321",
    app="AI_BIDIRECTIONAL_STREAM",
)
```
  

> **TIP:**
> If you build a customer-facing dashboard, mint a **short-lived** token per
> browser session. Do not embed a long-lived key. Supervisor attach and browser
> media legs already work this way.

## Isolation model

Every layer enforces tenancy, not only the application:

- **Naming.** Each tenant is a distinct SIP realm/AOR host (multidomain
  registrar). Thus two customers can both have extension `1001` with no
  collision.
- **Routing.** The engine derives `tenant_id` from the inbound INVITE's DNS
  label. Thus a misdirected trunk cannot land a call in the wrong tenant.
- **Data.** Reports and CDRs are read through a per-tenant analytics role. Its
  row policy pins `tenant_id = <org-id>`. There is no query path across
  tenants.
- **Credentials.** Every tenant secret (provider keys, trunk/registrar
  passwords) is **sealed at rest** with a key-encryption key. Provider keys are
  sealed under the org that owns them. Thus only the engine that handles that
  org can open them. See
  [voice isolation](/modalities/voice/concepts/voice-isolation) for the
  media-plane story.
- **Keyspace.** Per-tenant runtime state (sessions, registrations, sealed
  credentials, agent config) lives under org-scoped keys that the engine reads
  directly.

## Per-tenant agent config

Each customer configures their own agents: prompt, ASR/LLM/TTS providers,
voice, turn-detection, tools. On save, the platform hydrates the agent's config
into a single per-agent blob. The engine reads that blob at session start. The
blob survives many concurrent sessions for the same agent. Timeouts and
tunables hot-reload, and in-flight calls do not drop.

**Provider credentials resolve per tenant.** Thus customers can bring their own
keys or inherit the platform's. The resolution order at call time is:

1. **Per-agent override** — a key set on a specific agent.
2. **Per-tenant default** — the org's key for that provider.
3. **Platform fallback** — your environment default (if you offer one).

- **`Bring-your-own keys`** (`per provider`) — Each tenant can supply its own `openai` / `anthropic` / `gemini` /   `elevenlabs` / `deepgram` (and peers) credentials. The secret field is sealed   at rest. Only the non-secret routing fields (endpoint, region) stay   plaintext. See   [BYO ASR/LLM/TTS](/modalities/voice/runtime/byo-asr-llm-tts) and   [BYO speech-to-speech](/modalities/voice/runtime/byo-speech-to-speech).

You set turn detection per agent in the same way: `type: "silero"` (local VAD,
the cascaded default) or `type: "server_vad"` (the realtime provider owns
turns). See [turn detection](/modalities/voice/concepts/turn-detection).

## Billing & usage metering

Two independent meters feed platform billing. Both attribute usage per tenant:

- **Rated call usage.** On each call teardown, the engine sends a CDR (Q.850
  cause + duration, keyed by `call_sid`). The system rates CDRs against your
  tenant's usage account and rating plan. Thus per-minute call spend accrues
  automatically. The system captures media quality (MOS, jitter, loss) per call
  at the same time.
- **Traffic & bandwidth.** The engine counts bytes **per tenant, per modality**
  as they flow. It drains the counts into a per-minute rollup. That rollup
  powers the Traffic & Bandwidth dashboard. Voice media bytes are attributed
  per tenant, per call.

> **NOTE:**
> Entitlement tiers gate features and quotas in the control plane. The usage
> account meters spend. They are separate controls. A tier promotion does not
> change a tenant's rated usage. A balance top-up does not change their tier.

See [dashboards](/modalities/voice/observability/dashboards) and
[call traces](/modalities/voice/observability/call-traces) for how tenants see
their own usage. See
[deployment models](/modalities/voice/concepts/deployment-models) for how to
run this managed or on a customer's own infrastructure.

## Related

  - **[Build a contact-center platform](/modalities/voice/build/contact-center-platforms)** — Add ACD, skills, VDN/IVR, and supervisor tools on top of tenancy.
  - **[Build a voice agent](/modalities/voice/build/voice-agents)** — The single-agent build that each tenant configures.
  - **[Platform backend recipe](/modalities/voice/recipes/platform-backend)** — A runnable backend that provisions and drives tenants.
  - **[Runtime configuration](/modalities/voice/runtime/configuration)** — The per-agent pipeline config that each tenant edits.
