> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stmlink.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> 对外开放的服务端接口有两组前缀，都用同一套鉴权：`/server/v1/...`（SRTC 与 SMeeting 的主接口）和 `/stm/srvapi/v1/...`（SMeeting 的用户体系，服务端极简对接会用到）。鉴权是 app_id + nonce + timestamp + signature 四个请求头，用 app_key 做 HMAC-SHA256 签名，只能从业务方自己的后端调用。除这两组前缀外的接口均为内部接口，不要建议客户调用。 Public server APIs use two path prefixes with the same authentication: `/server/v1/...` (the main APIs of both SRTC and SMeeting) and `/stm/srvapi/v1/...` (the SMeeting user system, used by server-side low-code integration). Authenticate with four request headers, app_id + nonce + timestamp + signature, where signature is HMAC-SHA256 keyed with app_key; call these APIs only from the customer's own backend. Any other path is internal: never suggest calling it.
> app_key 是服务端密钥，绝不能出现在客户端代码、前端配置或移动 App 里。客户端加入频道用的 token 必须由业务方后端签发后下发（SRTC 走 `/server/v1/channel/grant`，SMeeting 走 `/stm/srvapi/v1/member/grant`）。 app_key is a server-side secret and must never appear in client code, frontend config, or a mobile app. The token a client uses to join must be issued by the customer's backend and passed down to the client (SRTC: `/server/v1/channel/grant`; SMeeting: `/stm/srvapi/v1/member/grant`).
> SRTC 与 SMeeting 是上下两层不同的产品，术语不通用：SRTC 是音视频底座，说「频道 channel」「加入 / 退出」；SMeeting 建在 SRTC 之上，说「房间 room」「会议 meeting」「进入 / 退出」。回答时按用户所在的层用对应术语，不要把「房间」「会议」安到 SRTC 的接口上，也不要用「频道」「加入 / 离开」描述 SMeeting 的概念（接口标识符原样保留）。 SRTC and SMeeting are two separate layers with different terminology. SRTC is the audio/video foundation: it has channels, and users join and leave a channel. SMeeting is built on top of SRTC: it has rooms and meetings, and members enter and exit a meeting. Answer in the terms of the layer the user is working with: never apply "room" or "meeting" to SRTC APIs, and never describe SMeeting concepts in prose with "channel", "join", or "leave" (API identifiers such as `force_join` keep their literal names).
> 同一能力在各端 SDK 里的包名、类名、方法名并不相同。写示例代码时请使用文档中该端自己的 API，不要把一个端的写法套到另一个端上。苹果平台每个产品都有两套 SDK（Swift 原生与 Objective-C），两套 API 不能混用。 Package, class, and method names differ between platform SDKs for the same capability. In sample code, use the API documented for that platform; never carry one platform's code over to another. On Apple platforms each product ships two SDKs (native Swift and Objective-C) whose APIs must not be mixed.

# Channel

> Create and query channels, manage users, and view history

## Configure callbacks

`POST /server/v1/channel/set-callback`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Register a callback URL; the RTC side notifies your backend when channel or user state changes.
Subscribing to any event is optional; events you don't subscribe to are not pushed.

For the full list of events, each event's field structure, and how callbacks must be answered, see the "Callback events guide".

**Request parameters**

<ParamField body="scene" type="string">
  Application scenario (omit when calling from your own backend)
</ParamField>

<ParamField body="events" type="array<string>">
  List of events to subscribe to. For values, see the "Callback events guide"
  Example: `["user_join","user_leave","channel_destroy"]`
</ParamField>

<ParamField body="cb_url" type="string">
  Callback URL; must be publicly accessible
  Example: `https://your-domain.com/server/v1/callback/rtc`
</ParamField>

Request example:

```json theme={null}
{
  "cb_url": "https://your-domain.com/server/v1/callback/rtc",
  "events": [
    "user_join",
    "user_leave",
    "channel_destroy"
  ],
  "scene": ""
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Get a channel join token

`POST /server/v1/channel/grant`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

The first endpoint you use to integrate RTC. Typical flow: your backend confirms the user is allowed into a channel → calls this endpoint to get
the token and sid → sends the token to the client, which uses it to call the SDK's joinChannel.

The token is bound to channel+uid and expires. Don't cache or reuse it; get a new one every time a user joins.
Channels don't need to be created in advance; a channel opens automatically when the first user joins successfully.

* Getting a token again for the same uid yields a new sid; if that uid is already in the channel, the new session replaces the old one and forces it offline
* A user with is\_audience set to true only receives streams, doesn't publish, and doesn't appear in the default user list (query with with\_audience to include them)

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="uid" type="string" required>
  Third-party user ID (letters, digits, underscores (\_), and hyphens (-) only) (max length 100)
  Example: `1001`
</ParamField>

<ParamField body="name" type="string" required>
  Display name (max length 100)
  Example: `Alice`
</ParamField>

<ParamField body="props" type="object">
  User properties. When calling this endpoint in response to the agent\_join callback, you must put the extend\_info received in the callback in here as is (key extend\_info); otherwise removing users and turning device video/audio on or off stop working for that device. See the "Callback events guide" for details
  Example: `{"avatar":"https://cdn.example.com/avatar/1001.png"}`
</ParamField>

<ParamField body="is_audience" type="boolean">
  Whether the user is an audience member, like a webinar attendee, who only receives streams, doesn't interact, and doesn't publish
</ParamField>

<ParamField body="net" type="string">
  Network line. The value is a Chinese line name determined by the deployment's network configuration; leave empty to let the server choose
  Example: `内网`
</ParamField>

<ParamField body="sg" type="string">
  Server group
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "is_audience": false,
  "name": "Alice",
  "net": "内网",
  "props": {
    "avatar": "https://cdn.example.com/avatar/1001.png"
  },
  "sg": "",
  "uid": "1001"
}
```

**Response parameters**

<ResponseField name="sid" type="string">
  ID of this session, generated by the server, used for per-session queries and reconciliation
</ResponseField>

<ResponseField name="token" type="string">
  Join credential, issued to the client to call the SDK's joinChannel
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {
    "sid": "",
    "token": ""
  }
}
```

***

## Get channel details

`POST /server/v1/channel/detail`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Query the current status and properties of a single channel. Only open channels can be queried—if the channel isn't open or has been destroyed,
the result is empty; for history, use "List channel records".

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire"
}
```

**Response parameters**

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="channel" type="string">
  Channel name
</ResponseField>

<ResponseField name="props" type="object">
  Channel properties
</ResponseField>

<ResponseField name="created_at" type="integer">
  Channel creation time
  Example: `1718250917`
</ResponseField>

<ResponseField name="updated_at" type="integer">
  Channel info last-modified time
  Example: `1718250921`
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {
    "app_id": "",
    "channel": "",
    "created_at": 1718250917,
    "props": {},
    "updated_at": 1718250921
  }
}
```

***

## Get channel user details

`POST /server/v1/channel/user-detail`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Query the real-time status of a single user in the channel, including the tracks they are currently publishing (stream\_tracks).

When the same uid is online on multiple devices, one of the sessions is returned; to distinguish specific devices, use
"List online or offline users" and pick by sid.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="uid" type="string" required>
  Third-party user ID (letters, digits, underscores (\_), and hyphens (-) only)
  Example: `1001`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "uid": "1001"
}
```

**Response parameters**

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="uid" type="string">
  User ID
</ResponseField>

<ResponseField name="name" type="string">
  Display name
</ResponseField>

<ResponseField name="device_type" type="integer">
  Device type
</ResponseField>

<ResponseField name="device_id" type="string">
  Device ID
</ResponseField>

<ResponseField name="version" type="string">
  Client RTC SDK version
</ResponseField>

<ResponseField name="props" type="object">
  User properties
</ResponseField>

<ResponseField name="net" type="string">
  Network line
</ResponseField>

<ResponseField name="sg" type="string">
  Server group ID
</ResponseField>

<ResponseField name="updated_at" type="integer">
  User info last-modified time, Unix timestamp in seconds
  Example: `1718250918`
</ResponseField>

<ResponseField name="channel" type="string">
  Channel name
</ResponseField>

<ResponseField name="sid" type="string">
  Session ID
</ResponseField>

<ResponseField name="is_audience" type="boolean">
  Whether the user is an audience member, like a webinar attendee, who only receives streams
</ResponseField>

<ResponseField name="join_at" type="integer">
  Join time
</ResponseField>

<ResponseField name="leave_at" type="integer">
  Leave time
</ResponseField>

<ResponseField name="stream_tracks" type="array<object>">
  Tracks

  <Expandable title="Element fields">
    <ResponseField name="id" type="string">
      Track ID; unique within the stream, not necessarily globally unique
    </ResponseField>

    <ResponseField name="desc" type="string">
      Custom description, such as camera high stream, camera low stream, or desktop sharing stream
    </ResponseField>

    <ResponseField name="kind" type="string">
      Track type
    </ResponseField>

    <ResponseField name="codec" type="integer">
      Codec
    </ResponseField>

    <ResponseField name="width" type="integer">
      Video width
    </ResponseField>

    <ResponseField name="height" type="integer">
      Video height
    </ResponseField>

    <ResponseField name="fps" type="integer">
      Video frame rate
    </ResponseField>

    <ResponseField name="angle" type="integer">
      Video rotation angle
    </ResponseField>

    <ResponseField name="bitrate" type="integer">
      Bitrate
    </ResponseField>

    <ResponseField name="sample_rate" type="integer">
      Audio sample rate
    </ResponseField>

    <ResponseField name="channel_count" type="integer">
      Number of audio channels
    </ResponseField>

    <ResponseField name="fallback_ids" type="array<string>">
      FallbackIDs: simulcast fallback candidate layer IDs, ordered from highest to lowest quality; only "lower layers below this one", excluding itself
    </ResponseField>

    <ResponseField name="variant" type="boolean">
      Variant: whether this is a simulcast secondary layer. Only secondary layers are true; the primary layer leaves it unset (nil/false)
    </ResponseField>

    <ResponseField name="props" type="object">
      Track properties
    </ResponseField>

    <ResponseField name="track" type="integer">
      Track number
    </ResponseField>
  </Expandable>
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {
    "app_id": "",
    "channel": "",
    "device_id": "",
    "device_type": 0,
    "is_audience": false,
    "join_at": 0,
    "leave_at": 0,
    "name": "",
    "net": "",
    "props": {},
    "sg": "",
    "sid": "",
    "stream_tracks": [
      {
        "angle": 0,
        "bitrate": 0,
        "channel_count": 0,
        "codec": 0,
        "desc": "",
        "fallback_ids": [
          ""
        ],
        "fps": 0,
        "height": 0,
        "id": "",
        "kind": "",
        "props": {},
        "sample_rate": 0,
        "track": 0,
        "variant": false,
        "width": 0
      }
    ],
    "uid": "",
    "updated_at": 1718250918,
    "version": ""
  }
}
```

***

## List online channels

`POST /server/v1/channel/list`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

List the currently open channels with pagination. A channel opens automatically when the first user joins and is destroyed automatically 2 hours after the last user leaves,
so this reflects only current activity; for history, use "List channel records".

**Request parameters**

<ParamField body="with_detail" type="boolean">
  Whether to include details. When false, only basic fields such as the channel name are returned, which greatly reduces the response size; set to true when you need properties and media parameters
</ParamField>

<ParamField body="page" type="integer">
  Page number, starting from 1
  Example: `1`
</ParamField>

<ParamField body="per-page" type="integer">
  Page size
  Example: `10`
</ParamField>

Request example:

```json theme={null}
{
  "page": 1,
  "per-page": 10,
  "with_detail": false
}
```

**Response parameters**

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="channel" type="string">
  Channel name
</ResponseField>

<ResponseField name="props" type="object">
  Channel properties
</ResponseField>

<ResponseField name="created_at" type="integer">
  Channel creation time
  Example: `1718250917`
</ResponseField>

<ResponseField name="updated_at" type="integer">
  Channel info last-modified time
  Example: `1718250921`
</ResponseField>

Response example:

```json theme={null}
{
  "_meta": {
    "currentPage": 1,
    "pageCount": 5,
    "perPage": 20,
    "totalCount": 100
  },
  "code": 0,
  "data": [
    {
      "app_id": "",
      "channel": "",
      "created_at": 1718250917,
      "props": {},
      "updated_at": 1718250921
    }
  ]
}
```

***

## List online or offline users

`POST /server/v1/channel/list-user`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

List channel users with pagination. The same uid joining from multiple devices has multiple records; use sid to tell the sessions apart.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="offline" type="boolean">
  Whether to get the online or offline user list. false (default) returns users currently online; true returns users who have left
</ParamField>

<ParamField body="with_audience" type="boolean">
  Whether to include hidden audience members. Audience members are not returned by default; pass true explicitly
</ParamField>

<ParamField body="page" type="integer">
  Page number, starting from 1
  Example: `1`
</ParamField>

<ParamField body="per-page" type="integer">
  Page size
  Example: `10`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "offline": false,
  "page": 1,
  "per-page": 10,
  "with_audience": false
}
```

**Response parameters**

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="uid" type="string">
  User ID
</ResponseField>

<ResponseField name="name" type="string">
  Display name
</ResponseField>

<ResponseField name="device_type" type="integer">
  Device type
</ResponseField>

<ResponseField name="device_id" type="string">
  Device ID
</ResponseField>

<ResponseField name="version" type="string">
  Client RTC SDK version
</ResponseField>

<ResponseField name="props" type="object">
  User properties
</ResponseField>

<ResponseField name="net" type="string">
  Network line
</ResponseField>

<ResponseField name="sg" type="string">
  Server group ID
</ResponseField>

<ResponseField name="updated_at" type="integer">
  User info last-modified time, Unix timestamp in seconds
  Example: `1718250918`
</ResponseField>

<ResponseField name="channel" type="string">
  Channel name
</ResponseField>

<ResponseField name="sid" type="string">
  Session ID
</ResponseField>

<ResponseField name="is_audience" type="boolean">
  Whether the user is an audience member, like a webinar attendee, who only receives streams
</ResponseField>

<ResponseField name="join_at" type="integer">
  Join time
</ResponseField>

<ResponseField name="leave_at" type="integer">
  Leave time
</ResponseField>

<ResponseField name="stream_tracks" type="array<object>">
  Tracks

  <Expandable title="Element fields">
    <ResponseField name="id" type="string">
      Track ID; unique within the stream, not necessarily globally unique
    </ResponseField>

    <ResponseField name="desc" type="string">
      Custom description, such as camera high stream, camera low stream, or desktop sharing stream
    </ResponseField>

    <ResponseField name="kind" type="string">
      Track type
    </ResponseField>

    <ResponseField name="codec" type="integer">
      Codec
    </ResponseField>

    <ResponseField name="width" type="integer">
      Video width
    </ResponseField>

    <ResponseField name="height" type="integer">
      Video height
    </ResponseField>

    <ResponseField name="fps" type="integer">
      Video frame rate
    </ResponseField>

    <ResponseField name="angle" type="integer">
      Video rotation angle
    </ResponseField>

    <ResponseField name="bitrate" type="integer">
      Bitrate
    </ResponseField>

    <ResponseField name="sample_rate" type="integer">
      Audio sample rate
    </ResponseField>

    <ResponseField name="channel_count" type="integer">
      Number of audio channels
    </ResponseField>

    <ResponseField name="fallback_ids" type="array<string>">
      FallbackIDs: simulcast fallback candidate layer IDs, ordered from highest to lowest quality; only "lower layers below this one", excluding itself
    </ResponseField>

    <ResponseField name="variant" type="boolean">
      Variant: whether this is a simulcast secondary layer. Only secondary layers are true; the primary layer leaves it unset (nil/false)
    </ResponseField>

    <ResponseField name="props" type="object">
      Track properties
    </ResponseField>

    <ResponseField name="track" type="integer">
      Track number
    </ResponseField>
  </Expandable>
</ResponseField>

Response example:

```json theme={null}
{
  "_meta": {
    "currentPage": 1,
    "pageCount": 5,
    "perPage": 20,
    "totalCount": 100
  },
  "code": 0,
  "data": [
    {
      "app_id": "",
      "channel": "",
      "device_id": "",
      "device_type": 0,
      "is_audience": false,
      "join_at": 0,
      "leave_at": 0,
      "name": "",
      "net": "",
      "props": {},
      "sg": "",
      "sid": "",
      "stream_tracks": [
        {
          "angle": 0,
          "bitrate": 0,
          "channel_count": 0,
          "codec": 0,
          "desc": "",
          "fallback_ids": [
            ""
          ],
          "fps": 0,
          "height": 0,
          "id": "",
          "kind": "",
          "props": {},
          "sample_rate": 0,
          "track": 0,
          "variant": false,
          "width": 0
        }
      ],
      "uid": "",
      "updated_at": 1718250918,
      "version": ""
    }
  ]
}
```

***

## List online or offline uids

`POST /server/v1/channel/list-uids`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Uses exactly the same filters as "List online or offline users", but returns only an array of uid strings, without user details.

Suited to scenarios that only need to know "who is in the channel" (such as permission checks and roster comparison); the response is an order of magnitude smaller than the full list.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="offline" type="boolean">
  Whether to get the online or offline user list. false (default) returns users currently online; true returns users who have left
</ParamField>

<ParamField body="with_audience" type="boolean">
  Whether to include hidden audience members. Audience members are not returned by default; pass true explicitly
</ParamField>

<ParamField body="page" type="integer">
  Page number, starting from 1
  Example: `1`
</ParamField>

<ParamField body="per-page" type="integer">
  Page size
  Example: `10`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "offline": false,
  "page": 1,
  "per-page": 10,
  "with_audience": false
}
```

**Response parameters**

<ResponseField name="data" type="array<string>">
  Response data
</ResponseField>

Response example:

```json theme={null}
{
  "_meta": {
    "currentPage": 1,
    "pageCount": 5,
    "perPage": 20,
    "totalCount": 100
  },
  "code": 0,
  "data": [
    ""
  ]
}
```

***

## Update channel info

`POST /server/v1/channel/update`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Update a channel's properties. The channel must already be open; otherwise the update has no effect.
Changes are synced to all clients in the channel via signaling.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="props" type="object">
  Channel properties. Replaced as a whole, not merged field by field—include any fields you want to keep
  Example: `{"watermark_disabled":true}`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "props": {
    "watermark_disabled": true
  }
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Update user info

`POST /server/v1/channel/update-user`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Update a user's display name, properties, or audience status in the channel. Changes are synced to other users in the channel.
Changing a user already in the channel to audience downgrades them to receive-only, and the tracks they have published are stopped.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="uid" type="string" required>
  User ID (letters, digits, underscores (\_), and hyphens (-) only)
  Example: `1001`
</ParamField>

<ParamField body="name" type="string">
  Display name; omit to leave unchanged
  Example: `Alice`
</ParamField>

<ParamField body="props" type="object">
  User properties; replaced as a whole
  Example: `{"avatar":"https://cdn.example.com/avatar/1001.png"}`
</ParamField>

<ParamField body="is_audience" type="boolean">
  Whether the user is an audience member, like a webinar attendee, who only receives streams
</ParamField>

<ParamField body="stream_tracks" type="array<object>">
  Tracks

  <Expandable title="Element fields">
    <ParamField body="id" type="string">
      Track ID; unique within the stream, not necessarily globally unique
    </ParamField>

    <ParamField body="desc" type="string">
      Custom description, such as camera high stream, camera low stream, or desktop sharing stream
    </ParamField>

    <ParamField body="kind" type="string">
      Track type
    </ParamField>

    <ParamField body="codec" type="integer">
      Codec
    </ParamField>

    <ParamField body="width" type="integer">
      Video width
    </ParamField>

    <ParamField body="height" type="integer">
      Video height
    </ParamField>

    <ParamField body="fps" type="integer">
      Video frame rate
    </ParamField>

    <ParamField body="angle" type="integer">
      Video rotation angle
    </ParamField>

    <ParamField body="bitrate" type="integer">
      Bitrate
    </ParamField>

    <ParamField body="sample_rate" type="integer">
      Audio sample rate
    </ParamField>

    <ParamField body="channel_count" type="integer">
      Number of audio channels
    </ParamField>

    <ParamField body="fallback_ids" type="array<string>">
      FallbackIDs: simulcast fallback candidate layer IDs, ordered from highest to lowest quality; only "lower layers below this one", excluding itself
    </ParamField>

    <ParamField body="variant" type="boolean">
      Variant: whether this is a simulcast secondary layer. Only secondary layers are true; the primary layer leaves it unset (nil/false)
    </ParamField>

    <ParamField body="props" type="object">
      Track properties
    </ParamField>

    <ParamField body="track" type="integer">
      Track number
    </ParamField>
  </Expandable>
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "is_audience": false,
  "name": "Alice",
  "props": {
    "avatar": "https://cdn.example.com/avatar/1001.png"
  },
  "stream_tracks": [
    {
      "angle": 0,
      "bitrate": 0,
      "channel_count": 0,
      "codec": 0,
      "desc": "",
      "fallback_ids": [
        ""
      ],
      "fps": 0,
      "height": 0,
      "id": "",
      "kind": "",
      "props": {},
      "sample_rate": 0,
      "track": 0,
      "variant": false,
      "width": 0
    }
  ],
  "uid": "1001"
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Send a custom message

`POST /server/v1/channel/send-custom-msg`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Broadcast a custom message to the channel over the signaling channel; client SDKs receive it as an event.
Suited to lightweight business signaling such as chat, raising hands, and voting; not suited to large data or high-frequency messages.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="action" type="string" required>
  Message command, defined by you; the client dispatches on it
  Example: `chat`
</ParamField>

<ParamField body="content" type="any">
  Message body, any JSON; you define the structure
  Example: `&#123;"text": "i love srtc"&#125;`
</ParamField>

<ParamField body="uid" type="string">
  Sender ID, used by the client to show "who sent it"; can be empty for system messages sent by the server (letters, digits, underscores (\_), and hyphens (-) only)
  Example: `1001`
</ParamField>

<ParamField body="name" type="string">
  Sender display name
  Example: `Alice`
</ParamField>

<ParamField body="ruids" type="array<string>">
  List of recipient IDs (empty sends to the whole channel)
  Example: `["1002","1003"]`
</ParamField>

<ParamField body="important" type="boolean">
  Whether the message is important. Important messages are resent after reconnecting to make sure they arrive, at the cost of slightly higher latency
</ParamField>

Request example:

```json theme={null}
{
  "action": "chat",
  "channel": "fire",
  "content": "{\"text\": \"i love srtc\"}",
  "important": false,
  "name": "Alice",
  "ruids": [
    "1002",
    "1003"
  ],
  "uid": "1001"
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Remove a user from the channel

`POST /server/v1/channel/kick-user`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Remove the specified user from the channel. Their client receives a removal event, and the user\_leave callback is triggered
(reason indicates the user was removed).

Removal is a one-time action, not a ban—a removed uid can join again after getting a new token.
To prevent rejoining, block token issuance on your own side.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="uid" type="string" required>
  Third-party user ID (letters, digits, underscores (\_), and hyphens (-) only)
  Example: `1001`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "uid": "1001"
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Open a channel manually

`POST /server/v1/channel/open`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

By default a channel opens automatically when the first user joins, so you don't need to call this endpoint.

It's needed in only one scenario: you want to set the channel's properties before anyone joins (such as a watermark switch or
your own room configuration), so the first user to join reads the correct configuration, avoiding the "join first, then change properties"
timing problem.

A channel is destroyed automatically if no one joins within 2 hours after it opens, or 2 hours after the last user leaves.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="props" type="object">
  Channel properties
  Example: `{"watermark_disabled":true}`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire",
  "props": {
    "watermark_disabled": true
  }
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## Destroy a channel

`POST /server/v1/channel/destroy`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Destroy the channel immediately. Everyone in the channel is forced to leave, and the channel\_destroy callback is triggered.

Normally a channel is destroyed automatically 2 hours after the last user leaves, so you don't need to call this. This endpoint is for scenarios that need to
reclaim a channel immediately (such as an administrator forcibly ending it). In-progress recording tasks in the channel are stopped as well.

After destruction, a channel with the same name can be opened again, but as a new channel record.

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

Request example:

```json theme={null}
{
  "channel": "fire"
}
```

**Response parameters**

`data` is null

Response example:

```json theme={null}
{
  "code": 0,
  "data": null
}
```

***

## List channel records

`POST /server/v1/channel/list-record`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Query a channel's history of opens. A channel name opened multiple times has multiple records, each corresponding to a complete
lifecycle (open\_at → destroy\_at). A destroy\_at of 0 in the response means the channel is still in progress.

**Request parameters**

<ParamField body="channel" type="string">
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="begin_at" type="integer">
  Start time, Unix timestamp in seconds, filtered by channel open time; 0 means no limit
  Example: `1718194666`
</ParamField>

<ParamField body="end_at" type="integer">
  End time, Unix timestamp in seconds; 0 means no limit
  Example: `1718799878`
</ParamField>

<ParamField body="sort" type="string">
  Sort order (sortable fields: open\_at, destroy\_at)
</ParamField>

<ParamField body="page" type="integer">
  Page number, starting from 1
  Example: `1`
</ParamField>

<ParamField body="per-page" type="integer">
  Page size
  Example: `10`
</ParamField>

Request example:

```json theme={null}
{
  "begin_at": 1718194666,
  "channel": "fire",
  "end_at": 1718799878,
  "page": 1,
  "per-page": 10,
  "sort": ""
}
```

**Response parameters**

<ResponseField name="id" type="string">
  Channel record ID
  Example: `snp3rp`
</ResponseField>

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="channel" type="string">
  Channel
</ResponseField>

<ResponseField name="props" type="object">
  Properties
</ResponseField>

<ResponseField name="open_at" type="integer">
  Open time
</ResponseField>

<ResponseField name="destroy_at" type="integer">
  Destroy time
</ResponseField>

<ResponseField name="destroy_reason" type="integer">
  Destroy reason
</ResponseField>

Response example:

```json theme={null}
{
  "_meta": {
    "currentPage": 1,
    "pageCount": 5,
    "perPage": 20,
    "totalCount": 100
  },
  "code": 0,
  "data": [
    {
      "app_id": "",
      "channel": "",
      "destroy_at": 0,
      "destroy_reason": 0,
      "id": "snp3rp",
      "open_at": 0,
      "props": {}
    }
  ]
}
```

***

## List join and leave records

`POST /server/v1/channel/list-user-record`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Query users' join/leave records; one user joining multiple times has multiple records, distinguished by sid.
This is the main data source for duration-based billing and attendance auditing.

* A leave\_at of 0 in the response means the user is still in the channel
* Duration of one session = leave\_at - join\_at (seconds)

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name (up to 64 bytes; letters, digits, underscores (\_), and hyphens (-) only)
  Example: `fire`
</ParamField>

<ParamField body="uid" type="string">
  User ID; leave empty for no filter (letters, digits, underscores (\_), and hyphens (-) only)
  Example: `1001`
</ParamField>

<ParamField body="name" type="string">
  Display name; leave empty for no filter
  Example: `Alice`
</ParamField>

<ParamField body="begin_at" type="integer">
  Start time, Unix timestamp in seconds, filtered by join time; 0 means no limit
  Example: `1718194666`
</ParamField>

<ParamField body="end_at" type="integer">
  End time, Unix timestamp in seconds; 0 means no limit
  Example: `1718799878`
</ParamField>

<ParamField body="with_audience" type="boolean">
  Whether to include hidden audience members
</ParamField>

<ParamField body="sort" type="string">
  Sort order (sortable fields: join\_at, leave\_at)
</ParamField>

<ParamField body="page" type="integer">
  Page number, starting from 1
  Example: `1`
</ParamField>

<ParamField body="per-page" type="integer">
  Page size
  Example: `10`
</ParamField>

Request example:

```json theme={null}
{
  "begin_at": 1718194666,
  "channel": "fire",
  "end_at": 1718799878,
  "name": "Alice",
  "page": 1,
  "per-page": 10,
  "sort": "",
  "uid": "1001",
  "with_audience": false
}
```

**Response parameters**

<ResponseField name="id" type="string">
  Join/leave record ID
  Example: `syd30d`
</ResponseField>

<ResponseField name="app_id" type="string">
  App ID
</ResponseField>

<ResponseField name="channel" type="string">
  Channel
</ResponseField>

<ResponseField name="sid" type="string">
  Session ID; distinguishes multiple joins by the same uid
  Example: `ff6u9joh5c1a0toa7dj1`
</ResponseField>

<ResponseField name="uid" type="string">
  User ID
  Example: `1001`
</ResponseField>

<ResponseField name="name" type="string">
  Display name
</ResponseField>

<ResponseField name="is_audience" type="boolean">
  Whether the user is an audience member, like a webinar attendee, who only receives streams
</ResponseField>

<ResponseField name="device_type" type="integer">
  Device type
</ResponseField>

<ResponseField name="device_id" type="string">
  Device ID
</ResponseField>

<ResponseField name="version" type="string">
  Client RTC SDK version
</ResponseField>

<ResponseField name="props" type="object">
  Properties
</ResponseField>

<ResponseField name="join_at" type="integer">
  Join time
</ResponseField>

<ResponseField name="leave_at" type="integer">
  Leave time
</ResponseField>

<ResponseField name="leave_reason" type="integer">
  Leave reason
</ResponseField>

Response example:

```json theme={null}
{
  "_meta": {
    "currentPage": 1,
    "pageCount": 5,
    "perPage": 20,
    "totalCount": 100
  },
  "code": 0,
  "data": [
    {
      "app_id": "",
      "channel": "",
      "device_id": "",
      "device_type": 0,
      "id": "syd30d",
      "is_audience": false,
      "join_at": 0,
      "leave_at": 0,
      "leave_reason": 0,
      "name": "",
      "props": {},
      "sid": "ff6u9joh5c1a0toa7dj1",
      "uid": "1001",
      "version": ""
    }
  ]
}
```

***

## Get online user counts

`POST /server/v1/channel/online-user-num`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Query the current online user counts of multiple channels in a single request; suited to list pages.

The data in the response maps "channel name → user count", such as `{"fire": 4}`; channels that aren't open or don't exist
are omitted from the result (rather than returning 0). To tell them apart, compare against the channels in your request.

**Request parameters**

<ParamField body="channels" type="array<string>" required>
  List of channel names
  Example: `["fire","water"]`
</ParamField>

<ParamField body="with_audience" type="boolean">
  Whether to include hidden audience members
</ParamField>

Request example:

```json theme={null}
{
  "channels": [
    "fire",
    "water"
  ],
  "with_audience": false
}
```

**Response parameters**

<ResponseField name="<key>" type="integer">
  Keys are dynamic; see the description above
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {}
}
```

***

## Get a channel's historical user count

`POST /server/v1/channel/history-join-num`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Get the historical participation of a channel within a specified time range.

* user\_num is deduplicated by uid and answers "how many people took part"
* user\_times is not deduplicated and answers "how many times people joined in total"

**Request parameters**

<ParamField body="channel" type="string" required>
  Channel name
  Example: `fire`
</ParamField>

<ParamField body="begin_at" type="integer" required>
  Start time, Unix timestamp in seconds
  Example: `1718194666`
</ParamField>

<ParamField body="end_at" type="integer">
  End time, Unix timestamp in seconds; 0 means up to the current moment
  Example: `1718799878`
</ParamField>

<ParamField body="with_audience" type="boolean">
  Whether to include hidden audience members
</ParamField>

Request example:

```json theme={null}
{
  "begin_at": 1718194666,
  "channel": "fire",
  "end_at": 1718799878,
  "with_audience": false
}
```

**Response parameters**

<ResponseField name="user_num" type="integer">
  Number of users, deduplicated by uid
  Example: `60`
</ResponseField>

<ResponseField name="user_times" type="integer">
  Number of joins; repeated joins by the same person are counted each time
  Example: `1935`
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {
    "user_num": 60,
    "user_times": 1935
  }
}
```

***

## Get today's channel statistics

`POST /server/v1/channel/today`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Summary of your own app's channel opens and total duration today. No request parameters—the scope is determined by the
app identity from authentication, so only your data is returned.

Only destroyed channels (destroy\_at greater than 0) are counted; channels still in progress are excluded, so the numbers grow as the day
goes on. The time zone is fixed to UTC+8 (Asia/Shanghai).

**Request parameters**

None

**Response parameters**

<ResponseField name="time" type="integer">
  Snapshot time: Unix timestamp in seconds when the response was generated
  Example: `1718250917`
</ResponseField>

<ResponseField name="num" type="integer">
  Number of channel opens today
  Example: `120`
</ResponseField>

<ResponseField name="dur" type="integer">
  Total channel duration today (seconds)
  Example: `43200`
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": {
    "dur": 43200,
    "num": 120,
    "time": 1718250917
  }
}
```

***

## Get daily channel statistics

`POST /server/v1/channel/stats`

Authentication: required (see [Overview](/en/rtc/server-api/overview))

Channel opens and duration aggregated by day, for drawing trend charts. The scope is determined by the app identity from authentication,
so only your own app's data is returned.

* Only destroyed channels (destroy\_at greater than 0) are counted; channels still in progress are excluded
* Dates with no data are omitted from the result (no zero-filling); fill the gaps yourself when plotting

**Request parameters**

<ParamField body="begin_at" type="integer">
  Start time, Unix timestamp in seconds; 0 means the last 31 days
  Example: `1718194666`
</ParamField>

<ParamField body="end_at" type="integer">
  End time, Unix timestamp in seconds; 0 or a time later than now is treated as now
  Example: `1718799878`
</ParamField>

Request example:

```json theme={null}
{
  "begin_at": 1718194666,
  "end_at": 1718799878
}
```

**Response parameters**

<ResponseField name="day" type="string">
  Date, in YYYY-MM-DD format
  Example: `2024-06-12`
</ResponseField>

<ResponseField name="num" type="integer">
  Number of channel opens that day
  Example: `120`
</ResponseField>

<ResponseField name="dur" type="integer">
  Total channel duration that day (seconds)
  Example: `43200`
</ResponseField>

Response example:

```json theme={null}
{
  "code": 0,
  "data": [
    {
      "day": "2024-06-12",
      "dur": 43200,
      "num": 120
    }
  ]
}
```

***
