You are the platform. Your customers sign up, bring their own numbers and provider keys, and configure their own agents. You bill them for usage. All of this runs on top of ClutchCall Voice. This recipe is the backend that makes a tenant real. It does four things:
  1. It provisions the tenant’s endpoints and data isolation.
  2. It mints a scoped key for that tenant.
  3. It publishes the tenant’s agent config.
  4. It drives calls with the tenant’s own key.
Everything keys off one stable identifier: the org id. The per-tenant SIP realm (<orgId>.sip.clutchcall.dev), the analytics row policy, sealed credentials, and the usage account are all keyed on it. A customer can therefore rename their workspace without a move of any infrastructure. For the conceptual model behind this code, read Building voice agent platforms first.

Two planes, two credentials

This backend uses two different credentials. A mix-up between them is the one mistake that breaks tenancy:
  • A platform control-plane credential provisions tenants and mints keys. It is privileged: it can create resources for any org. Keep it in your backend’s secret store. Never ship it to a browser. Never hand it to a customer.
  • A per-tenant scoped key drives one tenant’s calls. It resolves to exactly one org id. The control plane refuses it (403) outside that tenant’s scope. This is the key that you give a customer (or embed in a short-lived browser token).
The control plane lives at portal.clutchcall.dev. Per-tenant call and audio traffic goes through the SDK. The SDK talks to engine.clutchcall.dev and the media relay. Provisioning steps are idempotent. A re-run of the bootstrap backfills anything that was missed. Onboarding is therefore safe to retry.

1. A thin control-plane client

Provisioning is not in the SDK. It is a privileged control-plane surface that you call from your backend. This small wrapper posts to the control-plane API with your platform credential. Every helper below is a real provisioning route.

2. Onboard a tenant

One call to bootstrap fans out the per-tenant resources: the DNS labels (<orgId>.sip / <orgId>.webrtc / <orgId>) that resolve to the engine, the isolated analytics role with a row policy pinned to tenant_id, the usage account, and the tenant’s entitlement. Then you do five things:
  1. Register the tenant’s trunk.
  2. Map their DIDs.
  3. Seal any bring-your-own provider keys.
  4. Publish their agent.
  5. Mint the scoped key that their backend will use.
1

Bootstrap the org

Creates endpoints + data isolation, keyed on the org id.
2

Register trunk + numbers

Point their SIP trunk at their realm. Map DIDs to it.
3

Seal provider keys

Store BYO keys sealed under the org. This step is optional; tenants can inherit your keys.
4

Publish the agent

Hydrate the saved agent config to the engine.
5

Mint a scoped key

Hand the tenant a key bound to their org id.
hydrateAgent publishes an agent that your customer already authored (prompt, providers, voice, turn-detection, tools). The agent-authoring model is the pipeline config that each tenant edits. It lives in runtime configuration. BYO ASR/LLM/TTS covers the provider-key resolution order (per-agent → per-tenant → platform fallback).

3. Drive calls with the tenant’s key

Now switch planes. To place or manage a call for a tenant, instantiate the SDK with that tenant’s scoped key and org id. The org id is the boundary that the control plane enforces on every request. One tenant’s key can never address another tenant’s calls, agents, or audio.
Inbound calls need no per-call code at all. You mapped the DID to the agent at onboarding. The engine routes an INVITE to <orgId>.sip.clutchcall.dev by its leftmost DNS label to the tenant. It resolves the call to the agent. It answers the call. Your backend gets involved only for outbound calls, transfers, or supervision.

4. Wire it to an HTTP surface

Wrap the two functions above in two routes. That gives you a complete onboarding-and-dial backend. Guard the provisioning route with your own staff auth, because it holds the platform credential. Scope the dial route to the authenticated tenant.

Least-privilege scopes

Mint the narrowest scope set that each consumer needs. Scopes are product:action strings that the control plane checks per endpoint. The control plane refuses (403) a request that does not have the required scope.
scope
Read call state and history: status polls, listing, reports. Give this scope to a dashboard or a read-only integration.
scope
Originate, transfer, and hang up calls. Attach agents. This is the scope that a tenant’s call-driving backend needs.
scope
All voice actions. Use it rarely. Prefer the two explicit scopes above.
For a customer-facing dashboard, do not embed a long-lived tenant key in the browser. Mint a short-lived token per browser session, with only the scopes that the session needs. The supervisor-attach and browser-media legs already work this way. Revoke with revokeTenantKey when a customer offboards or rotates keys.

Metering is automatic

You do not write metering code. The engine attributes usage per tenant as calls run. On each teardown, it emits a CDR (Q.850 cause + duration, keyed by call_sid) rated against the tenant’s usage account. It also accounts media bytes per tenant per modality into a per-minute rollup. Both are keyed on the same org id. You can therefore read a tenant’s spend and bandwidth the moment their first call clears. See dashboards and call traces.

Building voice agent platforms

The concepts behind this code: tenancy, isolation, and metering.

Runtime configuration

The per-agent pipeline config each tenant authors.

SIP trunking

Register a tenant’s trunk against their realm.

Contact-center platform

Add ACD, skills, and supervisor tooling on top of tenancy.