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:
- 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.
- 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_participantsis enforced at join (409 call_full). A user rejoining doesn't count twice.