API overview
The Tring REST API is served by the tring container’s api role (port 8085 by default), on the same origin as the admin console (/admin/) and the browser SDK bundles (/sdk/). Everything your backend calls lives under the /v1 prefix.
Base URL
Section titled “Base URL”http://localhost:8085/v1 # local Docker Compose installhttps://api.example.com/v1 # behind your own TLS proxyThe examples in this reference use two environment variables:
export TRING_URL=https://api.example.comexport TRING_API_KEY=... # a tenant API key, see AuthenticationSo "$TRING_URL/v1/agents" is the agents collection.
Routes outside /v1
Section titled “Routes outside /v1”| Path | Auth | What it is |
|---|---|---|
GET /healthz |
none | Returns {"status":"ok"}, or 503 with {"error":"db unreachable"} when Postgres is down. Use it for load balancer health checks. |
/fs/* |
HTTP Basic (TRING_FS_BASIC_AUTH) |
Used by FreeSWITCH only, on the loopback listener TRING_FS_ADDR (not the public api port). Not for integrators. |
/sdk/* |
none | Browser widget bundles (voice.js, agent.js, tring.js). |
/admin/* |
none (the console signs in through the API) | The admin console. Served with a strict Content-Security-Policy that forbids framing; its connect-src allows the WebSocket origin of TRING_GATEWAY_PUBLIC_URL so supervisors can monitor calls. |
Authentication
Section titled “Authentication”Every /v1 route except POST /v1/console/login needs a credential in the Authorization: Bearer … header. Most routes take either a tenant API key (tk_live_…) or a console session token. A few take only an agent token or only a console session. See Authentication for which is which.
Everything is scoped to one tenant. The credential decides the tenant. No tenant ID goes in the path, and you can’t read or change another tenant’s data.
Requests
Section titled “Requests”- Send request bodies as JSON with
Content-Type: application/json. The one exception is audio upload, which ismultipart/form-data. - Field names are
snake_case. - Unknown fields in a request body are ignored.
- For
PATCHendpoints, a field you leave out keeps its current value. Send only the fields you want to change. - IDs are UUIDs. Two exceptions: teams are addressed by their name (
/v1/teams/{name}), and agent routes accept either the agent UUID or itssip_username(/v1/agents/{id}).
Responses
Section titled “Responses”- Successful responses are JSON (
Content-Type: application/json), except file downloads (audio, recordings) and the dashboard event stream. DELETEendpoints return204 No Contentwith an empty body.- Optional fields with no value may be left out of the response entirely, rather than sent as
nullor"". For example,display_nameis omitted on an agent that has none.
Timestamps
Section titled “Timestamps”Timestamps are RFC 3339 strings, for example 2026-10-16T09:41:07.512843+05:30. The UTC offset is the API server’s or database’s time zone, so parse them with a proper RFC 3339 parser rather than assuming Z.
Errors
Section titled “Errors”Every error the API itself returns uses the same shape: a JSON object with a single error string.
{ "error": "sip_username required" }Error messages are short, readable strings meant for logs and for showing to operators. They are not a stable machine contract. Branch on the HTTP status code, not the message text.
Status codes
Section titled “Status codes”| Status | Meaning |
|---|---|
200 OK |
Success, with a JSON body. |
201 Created |
A resource was created. The body is the new resource. |
204 No Content |
Success, no body (deletes, revokes). |
400 Bad Request |
Bad JSON, a missing required field, or a value that fails validation. |
401 Unauthorized |
Missing, unknown, expired or revoked credentials. Common messages: missing credentials, invalid credentials, invalid agent token, console session required. |
403 Forbidden |
Authenticated, but this user isn’t allowed to do this (for example, supervising without the permission). |
404 Not Found |
The resource doesn’t exist in your tenant. |
409 Conflict |
A uniqueness rule or a state check failed (for example, a duplicate name). |
413 Content Too Large |
The request body is over the 1 MB limit (see below). |
429 Too Many Requests |
Too many console login attempts. Wait for the number of seconds in Retry-After. |
500 Internal Server Error |
A server-side failure. The message is generic (for example update failed); the underlying error is written to the API server log, never to the response. |
503 Service Unavailable |
A dependency or feature isn’t available (for example, audio uploads with no audio directory configured). |
504 Gateway Timeout |
The request took longer than the 30-second limit (see below). |
Pagination
Section titled “Pagination”Most list endpoints (API keys, agents, teams, audio, numbers, trunks, webhooks) return a plain JSON array of every item in your tenant, with no pagination.
The endpoints that can grow large, calls and flows, return an envelope instead:
{ "items": [ ... ], "total": 1342, "limit": 25, "offset": 50}| Field | Description |
|---|---|
items |
The rows on this page. Always an array, never null. |
total |
Number of rows matching the query, before the page window was applied. |
limit |
Page size that was used. |
offset |
Number of rows skipped. |
Pass these query parameters to page through results:
| Param | Description |
|---|---|
page |
1-based page number. Including page turns on numbered pagination. |
limit |
Page size. Defaults to 25 when page is set. Capped at 200. |
curl -sS "$TRING_URL/v1/calls?page=3&limit=50" \ -H "Authorization: Bearer $TRING_API_KEY"Without page, each endpoint has its own default. See the calls and flows pages.
Timeouts and limits
Section titled “Timeouts and limits”- Request timeout: 30 seconds for every
/v1route exceptGET /v1/stream/dashboard, which is a long-lived stream. - Header read timeout: the server allows 5 seconds to receive request headers, and 30 seconds to receive the whole request including its body. Idle keep-alive connections are closed after 120 seconds.
- Body size: request bodies are capped at 1 MB. A larger body with a
Content-Lengthgets413; a larger chunked body fails to parse and gets400. Audio uploads have their own 10 MB cap instead. - Rate limits: only
POST /v1/console/loginis rate limited. If you expose an endpoint of your own that calls Tring for anonymous visitors (for example, minting guest credentials), add your own rate limit or CAPTCHA in front of it.
The API answers CORS preflight requests and echoes back any Origin. It allows the methods GET, POST, PATCH, DELETE, OPTIONS and the headers Authorization, Content-Type, X-API-Key. This is permissive by design for v0. It does not mean API keys belong in browsers: call the API from your backend, and give browsers only the short-lived tokens it mints.
Versioning
Section titled “Versioning”The only version is v1, and it’s in the path. There are no version headers. Write clients that ignore response fields they don’t recognise, so new fields don’t break them.