Overview
Request TURN credentials from your server, hand them to your client. Two endpoints, no SDK required.
Base URL
Authentication
Pass your API key as key in the JSON request body. Keys are per-app and available from the dashboard. Never send your API key from client-side code; request credentials from your own backend and forward them to the client.
Endpoints
| Endpoint | Purpose |
|---|---|
| POST /v1/credentials | Get TURN credentials for a user session. Supports regular credentials and gateway-pinned sessions. |
| POST /v1/exchange | Peer transport address discovery. Lets two clients find each other's address after connecting to a relay. |
Relay Selection
When you request credentials, Tosses geolocates the host_ip (or your server's IP if omitted) and routes to the closest healthy relay based on network proximity and real-time load. If multiple relays tie on location, the least-loaded one wins. Relay health is tracked continuously; overloaded or offline relays are excluded automatically.
IPs are never stored or logged, and are used only for routing purposes.
Quickstart
From zero to working TURN credentials in a few minutes.
1. Get an API Key
Sign up at the dashboard. Your first 1 GB is free with no card required. Create an app and copy the API key (it's only shown once).
2. Request Credentials (Server-Side)
3. Build the ICE Server Config
The response is already a valid ICE server object — pass it directly to the client:
Credential Lifetime
Credentials expire after expiry_ttl minutes (default 10). Expiry only prevents new allocations; active relay sessions aren't terminated when credentials expire. For long-lived sessions, refresh credentials before expiry and call pc.setConfiguration + pc.restartIce().
Credentials
Request TURN credentials for a single user session.
Request Body
| Field | Type | Description |
|---|---|---|
| key required | string | Your app's API key. |
| host_ip optional | string | The host peer's public IP address. Used for relay selection. Defaults to the IP the request came from. |
| expiry_ttl optional | string | Credential lifetime in minutes. Default is 10. Pass "Gateway" to create a gateway instead of regular credentials. Must be positive. |
| gateway optional | string | A gateway ID returned from a previous call with expiry_ttl: "Gateway". Returns 3-minute credentials pinned to that gateway's relay. |
Response: Regular Credentials
| Field | Description |
|---|---|
| username | TURN username. Format: {expiry_unix}:{app_id}_{random}. Also serves as the session identifier. |
| credential | TURN credential. base64(HMAC-SHA1(relay_secret, username)). |
| urls | Ready-to-use TURN URL array. Pass the entire response object directly as an ICE server entry. |
Gateways
A gateway is a persistent relay reservation. Every user who gets credentials through the same gateway is routed to the same relay, even if their credentials are requested at different times.
When to Use a Gateway
For multiplayer rooms or any session where multiple users need to share the same relay. Instead of generating one long-lived credential for the room (which expires), you generate a gateway once and hand each user their own short-lived credential from it.
Gateways never expire. Balance is checked each time a credential is issued through them, not upfront for the whole session.
Step 1: Create a Gateway
Response:
Store the gateway ID server-side for the lifetime of your room. It encodes the relay assignment and your app ID.
Step 2: Issue Credentials per User
Each time a user joins, call credentials with the gateway ID. You get back 3-minute credentials routed to the same relay as all other members of that gateway.
Response:
Relay Pinning Logic
When a gateway credential is requested, Tosses looks for any active allocations using that gateway. If found, it routes to that relay regardless of the original relay ID. If not, it picks the best available relay for the host IP, the same way a regular credential does.
This means even if the first user disconnects and a new one joins, as long as any session is active on the gateway's relay, that relay stays pinned.
Exchange
A lightweight peer discovery endpoint. After two clients connect to the same relay, they can use Exchange to find each other's transport address for direct peer-to-peer signaling.
Request Body
| Field | Type | Description |
|---|---|---|
| namespace required | string | A shared identifier both peers know, typically your session or room ID. |
| my_transport_address optional | string | Your transport address to publish (e.g. "203.0.113.42:53421"). Omit to only read without publishing. |
Response
Returns all addresses in the namespace except your own, keyed as peer_0, peer_1, etc. Addresses expire from the namespace after 10 minutes of no activity.
Usage Pattern
Have both clients poll Exchange after getting relay credentials. Each client publishes its address and reads the others. Once both addresses are available, use them to exchange WebRTC offers/answers out-of-band.
Billing & Credits
Usage is measured in egress GB: what our relays successfully send to the receiving peer. Bytes that never reach the other side aren't billed.
Two-Bucket Balance
Your account has two balance pools:
| Pool | Behavior |
|---|---|
| Subscription | Refills each month per your plan. Used first. Does not roll over. |
| PAYG | Purchased in bundles, never expires. Used automatically when subscription is empty. |
Balance Checks at Credential Time
Before issuing credentials, Tosses estimates whether your balance can cover the session. If the check fails, you get a 402.
The reserve requirement depends on your current active sessions and the requested TTL:
- Base reserve: 0.05 GB per currently active session.
- Long TTL (> 30 min): Additional 5 GB per hour of TTL beyond the default, unless your app has usage history; then it's estimated from your historical average throughput.
- Gateway calls: Reserve is calculated for 1 allocation only, not your total active session count.
These are reserves, not charges. Actual deductions happen based on real egress bytes at session close.
Negative Balance
Credential creation is blocked at zero balance. Your balance can go slightly negative if a session uses more than was reserved; you'll need to top up before issuing new credentials. Low-balance email notifications are configurable per account.
Error Reference
All errors return a JSON body with a detail field.
| Status | Meaning | Common causes |
|---|---|---|
| 400 | Bad Request | Missing key, invalid host_ip, non-positive expiry_ttl, malformed gateway ID. |
| 401 | Unauthorized | API key not found or invalid. |
| 402 | Payment Required | Balance too low to cover the estimated session reserve. Top up and retry. |
| 403 | Forbidden | App is paused, app is marked for deletion, or the gateway belongs to a different app. |