# Credentials & TTL

Every call to [`POST /credentials/ice`](https://turnix.io/docs/api-reference/credentials) creates a new, single-purpose TURN credential. This page explains its lifecycle and how to choose a TTL.

## What a credential is

A credential is a TURN username and password, bundled with the STUN and TURN URLs in WebRTC's `iceServers` format:

- **username**: a random UUID, unique per request
- **credential**: a random 32-character hex password
- **urls**: `stun:stun.turnix.io:3478`, plus four TURN URLs on the selected region's host `<region>.turnix.io`
- **expiresAt**: the moment the credential stops being accepted, in ISO 8601 UTC

Turnix stores the credential in your project together with the region, the request's country, and any [client and room labels](https://turnix.io/docs/concepts/clients-rooms). You can browse them under [Credentials](https://turnix.io/app/turn/credentials) in the dashboard.

## The TTL

`ttl` is the credential's lifetime in seconds. Turnix sets `expiresAt = now + ttl`.

| | |
|---|---|
| Format | Whole seconds, digits only (`3600`, not `3600.0` or `"1h"`). Anything else returns `422 {"error":"TTL must be an integer"}`. |
| Default | When you omit `ttl`, the platform default applies: **30 seconds**. |
| Bounds | The API does not enforce a minimum or maximum. A `ttl` of `0` creates a credential that has already expired. |

> **Warning: The default is very short**
>
> 30 seconds is only enough if the client uses the credentials immediately. If there is any delay between fetching credentials and creating the peer connection (a lobby, a permission prompt, a slow signaling round trip), set `ttl` explicitly.

## When expiry matters

The TURN server checks `expiresAt` when a client authenticates to create a relay allocation. That happens when ICE gathering starts, and again on every ICE restart, because a restart creates new allocations.

A relay that was set up before `expiresAt` is not cut off when the credential expires; the call continues. What fails after expiry is setting up *new* relays with the same credential, for example a renegotiation or an ICE restart after a network change.

## Choosing a TTL

| Scenario | Suggested `ttl` |
|----------|-----------------|
| Credentials fetched right before `new RTCPeerConnection()` | `300`–`600` |
| Typical calls, credentials fetched when the user joins | `3600` |
| Long sessions where you cannot refresh before an ICE restart | Up to the longest expected session, for example `14400` |

Shorter is safer: a leaked credential can relay traffic, billed to you, until it expires. Longer is more forgiving of delays. Most apps are well served by `3600` plus a refresh before ICE restarts.

## Refreshing credentials

- **New connection:** request new credentials for every new `RTCPeerConnection`. Requests are cheap and each one gets its own username.
- **ICE restart:** if `expiresAt` has passed or is close, fetch new credentials first and update the connection:

```javascript
async function restartIceWithFreshCredentials(pc) {
  const { iceServers } = await fetch("/api/turn-credentials").then((r) => r.json());
  pc.setConfiguration({ ...pc.getConfiguration(), iceServers });
  pc.restartIce();
}
```

- **Do not** cache credentials in `localStorage` or reuse them across users.

## Security checklist

- Request credentials from your backend only. The [API token](https://turnix.io/docs/api-reference/authentication) must never reach a browser or app.
- Protect your credentials endpoint with your app's own authentication, so strangers cannot use it to get free TURN relay.
- Prefer short TTLs and refresh instead of long-lived credentials.
- If software you do not control needs TURN access (Nextcloud Talk, Jitsi), use [shared-secret credentials](https://turnix.io/docs/integrations/shared-secret) instead of pasting a long-lived credential into its config.
