Client channel sessions
This page covers the HTTP APIs clients use to join, stay in and leave channels. The client SDKs already wrap these APIs along with the media connection, so you'll usually just use an SDK; you only need this page when implementing your own client.
These APIs are called with the User Token of the user's current IM sign-in; the App Token, channel tickets and media tokens can't replace it. They're plain HTTP requests and don't require an IM WebSocket connection first.
A complete meeting session:
| Step | API | Description |
|---|---|---|
| 1 | Join a channel | Returns the session session and media credential media; the session is joining |
| 2 | Connect to the media service with media | Once the server observes the connection, the session becomes joined, and only then are you really in |
| 3 | Call Heartbeat every 10 to 15 seconds | Sessions are removed after 45 seconds without a heartbeat |
| 4 | Renew the media credential before it expires | Credentials are usually valid for 60 seconds |
| 5 | Leave when done | Disconnect media and stop capture first, then call leave |
When you're unsure of a previous request's outcome (for example after a network drop), use Get your current session to find your session and keep using it, instead of joining again.
Join a channel
/client/v1/rtc/channels/{channel_id}/joinPath parameters
| Parameter | Type | Description |
|---|---|---|
channel_id | String | Channel ID, see Channel management |
The app and user come from the User Token and can't be specified in the request.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
client_session_id | String | Yes | ID the client generates for this join, 1 to 64 printable ASCII characters, no spaces. Keep it the same when retrying the same join |
ticket | String | Required for ticket channels | The ticket your server issued to this user; don't send it for open channels |
Example request
$USER_TOKEN is the current user's sign-in token, and $RTC_TICKET the ticket your server gave them:
curl -X POST "$IM_API/client/v1/rtc/channels/meeting-demo/join" \
-H "Authorization: Bearer $USER_TOKEN" -H "Content-Type: application/json" \
-d "{\"client_session_id\":\"join-meeting-demo-001\",\"ticket\":\"$RTC_TICKET\"}"Response
Returns 201 Created on first success, 200 OK on retries with the same client_session_id. The body contains:
| Field | Description |
|---|---|
channel | Channel object |
session | Your session, usually joining |
media | Media credential for connecting to the media service |
heartbeat_interval_seconds | Heartbeat interval suggested by the server |
Getting a media credential only means a seat is reserved for you: connect to the media service with it and wait until the session becomes joined before treating the join as complete.
While the media room is still being prepared, returns 503 unavailable with details.reason media_preparing and Retry-After; details may include your session_id and the deadline join_deadline:
- With a
session_id, continue that session via Renew the media credential; - Without one, retry the join with the same
client_session_idand ticket.
From joining to being in (preparing the room, connecting media, server confirmation) you have 60 seconds in total, and retries don't restart the clock. If you give up or it fails, stop capture immediately and call Leave.
Errors
| HTTP status | code | details.reason | What to do |
|---|---|---|---|
| 404 | not_found | channel_not_found | The channel doesn't exist, or there's no valid ticket. These aren't distinguished, see Client ticket errors |
| 403 | permission_denied | channel_banned, rtc_disabled and others | No permission, or the feature is off; don't retry automatically |
| 409 | conflict | media_resource_busy, joined_on_other_device | The user is in another call or channel, or already joined on another device |
| 409 | conflict | session_already_exists, idempotency_conflict | A session already exists, or the same client_session_id was used for a different request. Get the current session and continue the original join; don't switch to a new ID |
| 409 | conflict | channel_closed, session_left, join_deadline_exceeded | The channel is closed, the session has left, or the 60-second deadline passed. Stop this join and clean up |
| 409 | limit_exceeded | channel_full, active_media_limit | The channel is full or the app's concurrency quota is used up; try again when there's room |
| 503 | unavailable | media_preparing, media_reclaiming, media_unavailable, admission_unavailable | Temporarily unavailable. Retry a limited number of times with the same client_session_id, within the join deadline |
For other errors such as parameters, authentication and rate limiting, see Error codes.
Get your current session
Gets this sign-in's channel session, for finding your session when a request's outcome is uncertain. Only sessions of this sign-in (this device) are visible.
/client/v1/rtc/channel-session/currentExample request
curl "$IM_API/client/v1/rtc/channel-session/current" -H "Authorization: Bearer $USER_TOKEN"Response
Returns 200 OK; session is this sign-in's session, null if none.
Errors
For common errors such as an expired sign-in, see Error codes.
Get a channel
/client/v1/rtc/channels/{channel_id}Path parameters
channel_id as in Join a channel.
Example request
curl "$IM_API/client/v1/rtc/channels/meeting-demo" -H "Authorization: Bearer $USER_TOKEN"Response
Returns 200 OK with channel, without management information such as the close note.
Who can query: for open channels, any user of the app not banned from the channel; for ticket channels, only users whose current sign-in has a joining or joined session in the channel. Holding a ticket without having joined isn't enough.
Errors
Returns 404 not_found when you can't query it, the same as for a channel that doesn't exist.
List channel members
Lists members joining or joined. Only possible when this sign-in has a joining or joined session in the channel.
/client/v1/rtc/channels/{channel_id}/sessionsPath parameters
channel_id as in Join a channel.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Number | No | Items per page, see Pagination |
cursor | String | No | The next_cursor from the previous page |
status isn't supported.
Example request
curl "$IM_API/client/v1/rtc/channels/meeting-demo/sessions?limit=20" -H "Authorization: Bearer $USER_TOKEN"Response
Returns 200 OK:
| Field | Description |
|---|---|
items | Array of members, only those joining or joined. Members leaving aren't included but may still hold seats |
next_cursor | Cursor for the next page, null on the last page |
members_version | Member list version |
media_observed_at | When media state was last confirmed |
Errors
| HTTP status | code | Description |
|---|---|---|
| 404 | not_found | No permission to list members |
| 409 | version_conflict | details.reason is members_changed: members changed while paging. Start again from the first page; don't combine results from different versions |
Get your session
Gets one of your sessions, to check whether joining has become joined, or whether leaving has finished.
/client/v1/rtc/channels/{channel_id}/sessions/{session_id}Path parameters
| Parameter | Type | Description |
|---|---|---|
channel_id | String | Channel ID |
session_id | String | Session ID returned when joining. Store it as a string; don't convert it to a number or truncate it |
Example request
curl "$IM_API/client/v1/rtc/channels/meeting-demo/sessions/$RTC_SESSION_ID" -H "Authorization: Bearer $USER_TOKEN"Response
Returns 200 OK with session.
Errors
Returns 404 not_found with details.reason session_not_found when it isn't a session of this sign-in.
Renew the media credential
Gets a new media credential for your session that's still joining or joined. Doesn't use a ticket again or take a new seat.
/client/v1/rtc/channels/{channel_id}/sessions/{session_id}/tokenPath parameters and request body
Path parameters as in Get your session. The body is an empty object, or omitted.
Example request
curl -X POST "$IM_API/client/v1/rtc/channels/meeting-demo/sessions/$RTC_SESSION_ID/token" \
-H "Authorization: Bearer $USER_TOKEN" -H "Content-Type: application/json" -d '{}'Response
Returns 200 OK with session and a new media. Credentials are usually valid for 60 seconds: no later than the join deadline for joining sessions, and no later than the maximum session duration for joined ones. LiveKit's own connection refresh doesn't replace this API, where the server checks permissions again.
Errors
No renewal after the channel closes, the join times out, the session leaves, the user is banned or the sign-in is revoked; returns 503 while the media room is being prepared. Don't rejoin automatically after a refusal.
Heartbeat
Tells the server the client is still there. Whether media is actually connected is observed by the server itself; clients don't report it.
/client/v1/rtc/channels/{channel_id}/sessions/{session_id}/heartbeatPath parameters and request body
As in Renew the media credential, with an empty body.
Example request
curl -X POST "$IM_API/client/v1/rtc/channels/meeting-demo/sessions/$RTC_SESSION_ID/heartbeat" \
-H "Authorization: Bearer $USER_TOKEN" -H "Content-Type: application/json" -d '{}'Response
Returns 200 OK: session, channel status channel_status, channel version channel_version, member version members_version and server time server_time.
| Rule | Value |
|---|---|
| Interval suggested by the server | 15 seconds; current SDKs send about every 10 seconds |
| Removed after no heartbeat for | 45 seconds for joined sessions |
| Maximum time in a channel | 12 hours |
Heartbeats don't extend the 60-second join deadline, and can't revive a session that has already timed out.
Errors
Authentication, permission and session errors as in the APIs above.
- If the session in the response becomes
leavingorleft, or the channel is closed: stop media immediately. - If
channel_versionormembers_versionchanged: fetch the channel or members again.
Leave
/client/v1/rtc/channels/{channel_id}/sessions/{session_id}/leavePath parameters and request body
As in Renew the media credential, with an empty body.
Example request
curl -X POST "$IM_API/client/v1/rtc/channels/meeting-demo/sessions/$RTC_SESSION_ID/leave" \
-H "Authorization: Bearer $USER_TOKEN" -H "Content-Type: application/json" -d '{}'Response
Returns session: 202 Accepted with status leaving while still cleaning up, 200 OK with status left once done. Safe to call repeatedly.
Even if this request fails, the client should disconnect media and stop capture first. The server keeps reclaiming; until it's done, joining again may briefly report the user as in another channel.
Errors
Returns 404 not_found when it isn't a session of this sign-in. Treat an expired sign-in as an authentication error; don't switch to another user's sign-in to operate on the old session.
Data structures
Session
| Field | Description |
|---|---|
session_id | Session ID, a string |
channel_id, username, role | Channel, user and role |
status | joining, joined, leaving, left |
version | Session version |
reserved_at, join_deadline, max_expires_at | When the seat was reserved, the join deadline, the latest end time |
joined_at, leaving_at, left_at | When the user joined, started leaving, finished leaving |
leave_reason | Why the user left |
media_connected, media_observed_at | Media connection state as observed by the server, and when |
client_session_id | The ID submitted when joining, only in your own responses |
Participant
session_id, username, role, status, joined_at, version, media_connected, media_observed_at, media_identity. No device or sign-in information.
media_identity is the member's identifier in the media room, used to map received audio and video tracks to members exactly. It isn't the username and can't be used to join.
MediaCredential
provider (livekit), url, room, identity, token, expires_at. Only issued to the session's own user.
Time formats as in Server overview; times that haven't happened yet are null. media_connected is true, false or null, where null means it can't be confirmed for now. Don't persist tickets or media tokens; all these responses carry Cache-Control: no-store.
