Multi-Tenant & White-Label
SIP.IO is multi-tenant from the ground up, with first-class support for resellers and white-label operators. This page explains the account hierarchy and how tenancy flows through the system.
The account hierarchy
Section titled “The account hierarchy”An account is a tenant. Accounts form a tree via parent_id:
- A platform-direct account has
parent_id = NULL. - A reseller account (
is_reseller = 1) can own child accounts, brand them, and bill them.
Platform├── Account: Acme Corp (platform-direct)└── Account: VoiceReseller Inc. (is_reseller = 1) ├── Account: Bistro Group (white-labeled under VoiceReseller) └── Account: City ClinicEvery tenant row in the data model carries an explicit, indexed account_id, so tenancy is never implicit: it’s a column you can see, index, and enforce.
Per-account branding & defaults
Section titled “Per-account branding & defaults”Each account carries its own branding and defaults, which children inherit unless overridden:
| Field | Purpose |
|---|---|
brand_name, brand_domain | White-label brand and the portal/API host for a reseller. |
timezone | Default timezone for the account (used by schedules unless a schedule overrides it). |
language | Default prompt language (one of the 16 supported languages). |
voice_gender | Default narrator gender for system prompts (male / female). |
default_cid_number | Default outbound caller-ID number. |
did_default_dest_kind / did_default_dest_id | Where unrouted DIDs land. |
A reseller serves its own customers under its own SIP domains (sip_domain) and brand domain, so end customers never see the platform.
SIP domains & realms
Section titled “SIP domains & realms”An account can answer on several SIP domains: its own plus white-label vanity domains. The domain is the realm used for digest authentication, so 1002@acmeA and 1002@acmeB are distinct identities even with the same extension number. Registration lookups are domain-scoped (use_domain), keeping tenants cleanly separated at the registrar.
Per-account vanity realms (cust.sip.io)
Section titled “Per-account vanity realms (cust.sip.io)”For resellers and white-label customers who don’t want the SIP.IO brand showing up in their SIP addressing, an account can be given its own subdomain under a shared suffix, for example acme.cust.sip.io, following the same model as Twilio SIP Domains. This is not a separate feature from the SIP domains described above: a vanity realm is just another sip_domain row for the account, so it inherits the same digest-auth and domain-scoped registration behavior.
Provisioning is admin-gated (/admin/account-domain) and handles the details so a tenant can’t collide with the platform or with another tenant:
- Label derivation. A label can be supplied explicitly (a vanity request) or, if omitted, is derived from the account name.
- Slugify. Whatever text becomes the label is lowercased and reduced to a DNS-safe form (non-alphanumeric characters collapse to hyphens, leading/trailing hyphens trimmed, capped at 63 characters), so
Acme Corp!becomesacme-corp. - Reserved names blocked. Platform-significant labels (
api,nodes,console,realtime,sip,admin, and similar) are rejected so a tenant can’t grab a name that could be confused with platform infrastructure. - Uniqueness. An explicit vanity label must be free, an attempt to reuse one returns a conflict. An auto-derived label that collides is instead disambiguated automatically (a suffix from the account ID is appended) rather than failing, since the caller didn’t ask for that specific name.
- Primary flag. A domain can be marked primary for the account; the provisioning engine treats an account’s primary
sip_domainas its realm, so flipping this is what actually switches a tenant onto its vanity domain. - An account always keeps at least one domain: the last remaining domain on an account cannot be deleted.
The suffix itself is environment-configured (cust.sip.io in production), so the same code path also serves a separate dev suffix without any logic changes.
A vanity realm changes SIP addressing, not the TLS connection. The realm (acme.cust.sip.io) is what appears in the From/To and digest-auth identity, giving a reseller’s customers their own-looking SIP domain. It is deliberately not matched by a wildcard TLS certificate: RFC 5922 §7.2 forbids wildcard matching of SIP domain identities, so a *.cust.sip.io certificate wouldn’t be meaningful for proving the connection is genuine. In practice, provisioned phones and softphones connect over TLS/WSS to the platform’s own SBC hostname, not to the vanity realm, the SBC hostname is what the TLS certificate actually covers. The vanity domain travels inside the SIP signaling as the account’s realm; it isn’t used as the wire-level TLS endpoint. Per-domain certificates and SNI-based routing remain an option later for bring-your-own-domain or federation cases, but are not required for the common white-label case.
How tenancy is enforced
Section titled “How tenancy is enforced”Tenancy isn’t just a data convention: it’s enforced at each decision point:
/authresolves the device by(auth_username, realm)and returns the owningaccount_id; the SIP signaling layer binds that account context to the registration./routescopes every lookup byaccount_id: DIDs, extensions, outbound routes, transforms, and caller-ID rules are all matched within the tenant.- stateful edge objects are per-account. The
PresenceEngineandBalanceLedgerare addressed byaccount_id, so one tenant’s presence, ACD, CAC counters, and wallet are physically isolated in their own object.
Concurrency & spend per tenant
Section titled “Concurrency & spend per tenant”Each account has its own concurrency ceilings (max_in, max_out, max_dialer) and its own CAC counters in its PresenceEngine. Spend is governed by a separate retail wallet (BalanceLedger) plus the wholesale carrier’s authoritative balance, so one tenant can’t consume another’s capacity or budget.
Reseller billing
Section titled “Reseller billing”The account tree carries the billing relationship: a child account’s usage rolls up to its reseller parent. Wholesale termination and balance are handled by the carrier integration per subaccount, while the platform tracks the retail relationship.
Practical guidance
Section titled “Practical guidance”- Put each end customer in its own
account; give resellersis_reseller = 1and their ownbrand_domain. - Set the account’s
timezone,language, andvoice_genderonce, and schedules and prompts inherit them. - Use per-account
max_*ceilings as the first line of capacity isolation; layer rate rules on top for finer control.