Multi-tenancy and auth
Tenants
Section titled “Tenants”A tenant is one organization. Every agent, team, number, trunk, flow, sound, webhook, API key, console user and call belongs to exactly one tenant, and every API query is filtered by the tenant of the credential that made it. A tenant cannot read or change another tenant’s data, and gateway rooms are namespaced by tenant so two tenants can never share a call.
The first tenant and its console admin are created on first boot from TRING_BOOTSTRAP_TENANT, TRING_ADMIN_EMAIL and TRING_ADMIN_PASSWORD (see Configuration). There is no API for creating more tenants or console users yet.
Tenant-wide settings (GET / PATCH /v1/tenants/me): outbound_strategy, failover_causes, hold_music, record_calls, max_call_seconds (60–86400, default 14400; also the lifetime of ephemeral TURN credentials) and transfer_failed_message. The default visitor flow lives at /v1/guest-config. See the Tenant API.
Trunks are configured in the database per tenant, but they are all loaded into the one shared FreeSWITCH. Per-tenant trunk isolation and multi-region deployments are on the roadmap.
Credentials at a glance
Section titled “Credentials at a glance”| Credential | Looks like | Issued by | Lifetime | Accepted by | Grants |
|---|---|---|---|---|---|
| API key | tk_live_… |
Console (API keys) or POST /v1/api-keys |
Until revoked | The api (/v1/*) |
Full access to the tenant’s API |
| Console session | JWT, aud: tring.console |
POST /v1/console/login (email + password) |
12 hours | The api (/v1/*) |
Same as an API key, plus supervision if the user is permitted |
| Agent token | JWT, aud: tring.sdk |
GET /v1/agents/{id}/token (API key) |
5 minutes | Gateway /rtc; api /v1/sdk/* |
Go online as that agent, take calls, control the agent’s own call |
| Visitor call token | JWT, aud: tring.gateway |
POST /v1/guest-credentials (API key) |
2 minutes, single use | Gateway /rtc |
Join one call pinned to one flow |
| Supervisor token | JWT, aud: tring.supervisor |
POST /v1/calls/{id}/supervise (console session only) |
2 minutes, single use | Gateway /rtc |
Listen to, whisper on or barge into one live call |
| FreeSWITCH basic auth | user:password |
TRING_FS_BASIC_AUTH |
Static | The api (/fs/*) |
FreeSWITCH directory lookups. Internal only |
All JWTs are HS256, signed with TRING_JWT_SECRET, with issuer tring. Each kind has its own audience and is rejected anywhere else: an agent token cannot call the tenant API, and a visitor token cannot register as an agent.
Send any credential as Authorization: Bearer <credential>. The api also accepts X-API-Key: <key>. The gateway also accepts ?token=<token> on the /rtc WebSocket URL.
API keys
Section titled “API keys”For your servers: CRM backends, token endpoints, provisioning scripts.
- The full key is shown once, when it is created. Tring stores only its SHA-256 hash and a short prefix for display.
- Each use updates
last_used_at, shown in the key list, which helps you find unused keys. - Revoke with
DELETE /v1/api-keys/{id}. A revoked key stops working immediately. - Managing keys requires an existing credential of the same tenant (console session or another key).
export TRING_API_KEY="$(your-secret-manager get tring/api-key)"curl -sS https://api.example.com/v1/agents -H "Authorization: Bearer $TRING_API_KEY"Console sessions
Section titled “Console sessions”The admin console signs in with email and password (bcrypt-hashed) and holds a 12-hour session token. There is no refresh token; signing in again issues a new one. Failed logins return the same generic error whether the email or the password was wrong.
Only console sessions can supervise calls, and only for users with the supervise permission (the bootstrap admin has it; it is off by default for others).
Agent tokens
Section titled “Agent tokens”Your backend mints an agent token for the agent who is signed in to your app, and passes it to their browser through your own endpoint (the agent widget’s data-token-url). The token response also carries gateway_url and ice_servers, so the page needs nothing else.
// Your server (Node.js, built-in fetch). Check YOUR session first.app.get("/tring/token", requireLogin, async (req, res) => { const r = await fetch( `https://api.example.com/v1/agents/${encodeURIComponent(req.user.crmId)}/token`, { headers: { Authorization: `Bearer ${process.env.TRING_API_KEY}` } }, ); res.status(r.status).type("application/json").send(await r.text());});Mint tokens only for the user who is actually signed in, never for an id taken from the request. The 5-minute lifetime limits the damage of a leaked token; the widget fetches a new one when it needs to. See the agent widget.
With an agent token, a browser can only act as that agent: go online, accept or decline rings, and hold, resume or transfer its own call. It cannot place outbound calls or read anything else.
Visitor call tokens
Section titled “Visitor call tokens”Your backend mints one per call with POST /v1/guest-credentials and an API key, after deciding the visitor may call (see Web calling).
- The flow is pinned inside the token; the browser cannot choose another flow.
client_ref, if you pass it, is stored on the call as a trusted identity. Pass only identities your backend verified.- The gateway burns the token on first use (Redis), so a captured token cannot start a second call. A page refresh within the grace window reattaches with a separate session token instead.
- Visitor connections cannot transfer calls or dial numbers.
Because anyone can reach a public “call us” page, protect your credentials endpoint with rate limits and a CAPTCHA or session check before minting.
Least-privilege guidance
Section titled “Least-privilege guidance”- One API key per backend service, named after it, so you can revoke one without breaking others. Revoke keys whose
last_used_atis stale. - Keep API keys server-side. Browsers only ever receive agent, visitor and supervisor tokens, which are short-lived and narrowly scoped.
- Gate your token endpoints with your own authentication (agents) or abuse controls (visitors). Tring trusts whatever your backend asks for.
- Grant supervision sparingly. It allows silent listening to live calls.
- Protect
TRING_JWT_SECRET. It signs every token for every tenant. Store it in a secret manager and rotate it if exposed (rotation signs everyone out). - Use HTTPS everywhere in production: the api, the gateway (
wss://for/rtc), your token endpoints and webhook receivers. - Keep internals private.
/fs/*, FreeSWITCH ESL, Postgres and Redis must not be reachable from the internet. - Turn on MFA and audit logging for the cloud, registry and secret-manager accounts that hold these credentials.