Skip to content

Joining a call

Joining is a single API call that returns everything the media SDK needs.

Join endpoints

Endpoint Token Use when
POST /v1/invites/{code}/join user The user opened an invite link.
POST /v1/calls/{id}/join user The user is a member and your app already knows the call id, for example from a "My calls" list.
POST /v1/invites/{code}/join service Your backend redeems an invite on a user's behalf and passes the result to the app.
POST /v1/calls/{id}/join service Your backend joins on a user's behalf and passes the result to the app.

Send X-Client-Platform (web, android, ios, flutter-android, flutter-ios and so on; tele-flutter sends flutter-<os>) so the call record shows each participant's device.

curl -X POST $TELE_API/v1/invites/$CODE/join \
  -H "Authorization: Bearer $USER_TOKEN" \
  -H "X-Client-Platform: android"

Response

{
  "ws_url": "wss://tele-lk.cloudguys.io",
  "token": "eyJhbGciOi…",
  "token_expires_at": "2026-10-13T09:10:00Z",
  "ice_servers": [
    { "urls": ["stun:stun.cloudflare.com:3478"] },
    { "urls": ["turn:turn.cloudflare.com:3478?transport=udp", "turns:turn.cloudflare.com:443?transport=tcp"],
      "username": "…", "credential": "…" }
  ],
  "identity": "u:01a131d1-ae9d-70fa-8e9a-563b8324f088",
  "grant_id": "01a131d1-…",
  "role": "participant",
  "call": { "id": "…", "title": "…", "status": "live" }
}
Field Use
ws_url, token Pass to the media SDK's connect. The token is valid for 10 minutes to connect; the SDK refreshes it while you stay connected.
ice_servers Pass as the SDK's ICE server list. These are short-lived Cloudflare TURN credentials that let users behind strict firewalls connect. If null, don't pass any servers; the SDK uses its defaults.
role host or participant. The host's media token carries room-admin permission. Use role to decide who sees host controls such as End call (POST /v1/calls/{id}/end).
identity The participant's media identity. Map it back to your user through the participant attribute user_id.

ICE servers replace the defaults

Passing ice_servers replaces the SDK's built-in ICE server list. Pass the list as is: it already includes a STUN server. On Android, the ICE servers only take effect if you also set rtcConfig; see KMP.

Joining on a user's behalf (service token)

When your backend joins for a user, Tele sees your server's IP address, not the user's. You must pass the end user's IP and user agent:

curl -X POST $TELE_API/v1/calls/$CALL_ID/join -H "Authorization: Bearer $SERVICE_TOKEN" -d '{
  "user": { "user_id": "cust-9001", "name": "Customer", "ip": "203.0.113.50", "user_agent": "YourApp/3.2 (Android 15)" }
}'

The same user body works on POST /v1/invites/{code}/join, so a backend that redeems an invite for a browser records the user's IP too. The access policy still applies: in a listed_only call, the user must be a member.

Who sees whom

Other participants see the user's name (or their user_id when no name is set). Every participant also carries these attributes, readable from any SDK:

Attribute Value
user_id Your user id
platform The X-Client-Platform the user joined with
grant_id The join record (for support or debugging)

Reconnects

The media SDKs reconnect automatically after network blips. Two cases need your app's help:

  1. The SDK gives up reconnecting, for example after a long outage or once the TURN credentials have expired. Call the join endpoint again and reconnect. Get a fresh user token from your backend first if the old one is near expiry.
  2. The same user joins from a second device. The first device is disconnected (DUPLICATE_IDENTITY). Treat that as "you joined elsewhere" rather than as an error.

Limits

  • 20 join requests per minute per user. Beyond that, Tele returns 429 rate_limited.
  • max_participants is enforced at join (409 call_full). A user rejoining doesn't count twice.