Provisioning is triggered by org onboarding (the console) or by your platform
backend calling the control-plane API. Each step below is idempotent and
best-effort: onboarding completes even if one subsystem is briefly unavailable,
and re-running the bootstrap backfills whatever was missed.
What a tenant gets
Per-tenant SIP + WebRTC endpoints
<org-id>.sip.telequick.dev and <org-id>.webrtc.telequick.dev resolve to
the engine. Inbound SIP INVITEs are routed by their leftmost DNS label.Isolated analytics
A read-only analytics role scoped by a row policy on
tenant_id, so a
customer’s reports and CDRs can never read across the tenant boundary.A usage account
A metering account keyed
Tenant=<org-id> bound to your rating plan, so
every rated call debits the right balance from the first minute.Managed recording storage
A per-tenant object-storage prefix (
<org-id>/) + access keys. Leave a
customer’s recording target empty and recordings land in managed storage.starter | growth | scale | enterprise, defaulting to your configured
tier) and a per-tenant realtime channel key so CTI events and screen-pop have a
channel from day one.
Provision a tenant
Create the org and bootstrap resources
Onboarding creates the org record and fans out 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 keyed
on the new org id.
Point trunks and numbers at the tenant realm
Register the customer’s SIP trunk against their realm and map DIDs to it.
The engine extracts
<org-id> from the leftmost DNS label of each inbound
INVITE as the tenant_id, so no slug-to-id translation is ever needed. See
SIP trunking and
number provisioning.Publish the tenant's agent config
When a customer saves an agent, its pipeline config is hydrated to the
engine keyed per agent; provider credentials resolve per-tenant (below).
See runtime configuration.
Scoped API keys
Programmatic access uses opaque bearer tokens carried asAuthorization: Bearer <key>. Keys are stored hashed server-side (the raw
value is shown once at creation), carry a scope set, and can be given an
expiry and revoked at any time. The token resolves to exactly one org id, which
becomes the tenant boundary for every request it makes.
What the key may do, as
product:action strings — e.g. voice:read,
voice:write, or a wildcard voice:* / *. A request is refused (403) if
the key lacks the scope the endpoint requires.Optional hard expiry. Expired keys authenticate as invalid.
Set to revoke immediately without deleting the audit trail.
- TypeScript
- Python
Isolation model
Tenancy is enforced at every layer, not just in the application:- Naming. Each tenant is a distinct SIP realm/AOR host (multidomain
registrar), so two customers can both have extension
1001with no collision. - Routing. The engine derives
tenant_idfrom the inbound INVITE’s DNS label, so a misdirected trunk can’t land a call in the wrong tenant. - Data. Reports and CDRs are read through a per-tenant analytics role whose
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, and provider keys are sealed under the owning org so only the engine handling that org can open them. See voice isolation for the media-plane story.
- Keyspace. Per-tenant runtime state (sessions, registrations, sealed credentials, agent config) lives under org-scoped keys 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 agent’s config is hydrated into a single per-agent blob the engine reads at session start (it survives many concurrent sessions for the same agent). Timeouts and tunables hot-reload without dropping in-flight calls. Provider credentials resolve per tenant, so customers can bring their own keys or inherit the platform’s. Resolution order at call time:- Per-agent override — a key set on a specific agent.
- Per-tenant default — the org’s key for that provider.
- Platform fallback — your environment default (if you offer one).
Each tenant can supply its own
openai / anthropic / gemini /
elevenlabs / deepgram (and peers) credentials; the secret field is sealed
at rest and only the non-secret routing fields (endpoint, region) stay
plaintext. See
BYO ASR/LLM/TTS and
BYO speech-to-speech.type: "silero" (local
VAD, the cascaded default) or type: "server_vad" (the realtime provider owns
turns). See turn detection.
Billing & usage metering
Two independent meters feed platform billing, both attributed per tenant:- Rated call usage. On each call teardown the engine emits a CDR (Q.850
cause + duration, keyed by
call_sid). CDRs are rated against your tenant’s usage account and rating plan, so per-minute call spend accrues automatically. Media quality (MOS, jitter, loss) is captured per call alongside it. - Traffic & bandwidth. The engine accounts bytes per tenant, per modality as they flow, drained into a per-minute rollup that powers the Traffic & Bandwidth dashboard. Voice media bytes are attributed per tenant per call.
Entitlement tiers gate features and quotas in the control plane; the usage
account meters spend. They are separate knobs — promoting a tenant’s tier does
not change their rated usage, and topping up a balance does not change their
tier.
Related
Build a contact-center platform
Add ACD, skills, VDN/IVR, and supervisor tooling on top of tenancy.
Build a voice agent
The single-agent build that each tenant configures.
Platform backend recipe
Runnable backend that provisions and drives tenants.
Runtime configuration
The per-agent pipeline config each tenant edits.