Chat room messages
Send messages to chat rooms, read recent messages and recall violating ones from your server. Chat room messages only go to connections in the room right now: no offline messages, no unread counts, no offline push, and they don't enter conversations or message export.
| You want to | API |
|---|---|
| Send system notices, streamer announcements, gift settlements | Send a chat room message; use "priority": "high" for ones that mustn't be lost |
| Read a room's last few dozen messages | Query recent messages |
| Recall a violating message | Recall a chat room message |
For example, send a high-priority notice as the system:
curl -X POST "$IM_API/$ORG/$APP/chatrooms/100310941534519296/messages" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "text", "body": { "text": "直播将在 5 分钟后开始" }, "priority": "high" }'When your server sends, it doesn't check whether the sender is in the room, their role or mutes; see Rules for server-sent messages. For priorities and how messages are merged, throttled and dropped when busy, see Chat room management.
How they differ from one-to-one and group messages
| One-to-one and group messages | Chat room messages | |
|---|---|---|
| Recipients | Conversation members; offline devices catch up when back online | Connections in the room right now; messages sent while offline are never received |
| Storage | Stored in the conversation; history can be queried | Only recent messages are kept (the last 50 by default, for up to 24 hours); low-priority messages aren't kept |
| Types | See Message types | text, image, voice, video, file, location, custom, with the same body and ext formats; system-only types such as tip aren't supported |
| Size limit | max_message_body_bytes in the runtime policy, 5 KB by default | max_chatroom_message_bytes in the runtime policy, 2 KB by default, 256 bytes to 8 KB |
| Priority | None | high, normal, low; throttled and dropped by priority when busy |
| Sender info | Only the sender's username | The sender's nickname, avatar and room role at send time, so recipients needn't look them up |
| Other features | Mentions, replies, read receipts, editing, pinning, reactions, offline push | None of these; messages can only be recalled after sending |
| Deduplication | client_msg_id prevents duplicate writes | The same client_msg_id from the same sender within 5 minutes is sent once; low-priority messages aren't deduplicated |
Send a chat room message
Send a message to a chat room as a user or as the system (no sender; clients usually show it as a system notice). It's pushed immediately to every connection in the room, and normal and high-priority messages are also added to recent messages. The message's via is openapi, which clients can combine with sender_role to show badges such as "Official" or "Moderator"; they shouldn't trust identities claimed in ext.
/{org_name}/{app_name}/chatrooms/{room_id}/messagesPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
room_id | String | The chat room ID |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
from | String | No | The sender's username; must be an existing user but needn't be in the room. When omitted it's sent as the system, with sender null and sender_type system |
client_msg_id | String | No | A unique ID you generate, 1 to 64 visible ASCII characters, not starting with evt_ or sys_. The same client_msg_id from the same sender (the system counts as one sender) in the same room within 5 minutes is sent once, see repeated submissions below. When omitted the server generates a UUID, which doesn't prevent duplicates. Low-priority messages aren't deduplicated |
type | String | Yes | text, image, voice, video, file, location or custom, see Message types |
body | Object | Yes | The message body, in the format for type, the same as one-to-one and group messages, see Message types. Upload images, voice, video and files with purpose attachment first and put the addresses in body; the server only checks the address format |
ext | Object | No | Extension fields, a JSON object of your own, see Extension fields ext. Omit or pass null when not needed |
priority | String | No | high, normal (default) or low, see Message priority and delivery |
type, body and ext together, measured in the server's JSON encoding (no whitespace, UTF-8 bytes), can't exceed max_chatroom_message_bytes in the runtime policy (2048 bytes by default). The request can't include fields specific to one-to-one and group messages such as mentions, reply_to or push; those return 400 invalid_argument.
Request example
Send a high-priority notice as the system:
curl -X POST "$IM_API/$ORG/$APP/chatrooms/100310941534519296/messages" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_msg_id": "notice-20261004-001",
"type": "text",
"body": { "text": "直播将在 5 分钟后开始" },
"ext": { "kind": "live_notice" },
"priority": "high"
}'Send a gift message as the streamer (the owner):
curl -X POST "$IM_API/$ORG/$APP/chatrooms/100310941534519296/messages" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"from": "zhangsan",
"client_msg_id": "gift-8f3a91",
"type": "custom",
"body": { "event": "gift", "gift_id": "rocket", "count": 1 },
"ext": { "combo": 3 },
"priority": "high"
}'Response
On success you get 201 Created with the sent chat room message object plus one field:
| Field | Type | Description |
|---|---|---|
dropped | Boolean | Whether a low-priority message was dropped by rate limiting. false for newly sent messages |
The response when sending as the system:
{
"room_id": "100310941534519296",
"message_id": "100312324253941760",
"client_msg_id": "notice-20261004-001",
"sender": null,
"sender_type": "system",
"sender_nickname": null,
"sender_avatar_url": null,
"sender_role": null,
"type": "text",
"body": { "text": "直播将在 5 分钟后开始" },
"ext": { "kind": "live_notice" },
"priority": "high",
"via": "openapi",
"created_at": "2026-10-04T19:25:21.815Z",
"dropped": false
}The response when sending as a user:
{
"room_id": "100310941534519296",
"message_id": "100312466948358144",
"client_msg_id": "gift-8f3a91",
"sender": "zhangsan",
"sender_type": "user",
"sender_nickname": "张三",
"sender_avatar_url": "https://cdn.example.com/avatar/zhangsan.png",
"sender_role": "owner",
"type": "custom",
"body": { "event": "gift", "gift_id": "rocket", "count": 1 },
"ext": { "combo": 3 },
"priority": "high",
"via": "openapi",
"created_at": "2026-10-04T19:25:55.836Z",
"dropped": false
}Repeated submissions: retrying with the same client_msg_id within 5 minutes returns 200 OK without pushing the message again, with the original message_id and created_at. Rely on those two fields: the other fields come from this request, and sender fields (sender and so on) may be empty.
Dropped low-priority messages: over the room's per-second message quota you get 200 OK with dropped true and message_id and created_at null; the message wasn't sent. Losing likes and the like is fine, so don't retry:
{
"client_msg_id": null,
"created_at": null,
"dropped": true,
"message_id": null,
"priority": "low",
"room_id": "100311027198984192"
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | details.reason unknown_type: an unsupported type, including tip |
| 400 | invalid_argument | details.reason invalid_body: body or ext is invalid; details.field names the field |
| 400 | invalid_argument | details.reason invalid_client_msg_id: client_msg_id isn't 1 to 64 visible ASCII characters |
| 400 | invalid_argument | details.reason reserved_client_msg_id: client_msg_id starts with evt_ or sys_ |
| 400 | invalid_argument | details.reason invalid_priority: priority isn't high, normal or low |
| 403 | user_disabled | The sender is banned by the platform; details.disabled_by is platform |
| 403 | chatroom_disabled | The room is banned; details.disabled_by is tenant (banned by you) or platform |
| 403 | message_rejected | Rejected by the pre-send checks: moderation gives details.reason sensitive_content (word lists only; third-party review runs after the message is pushed); the before-send-chat-room-message callback gives app_rejected or callback_unavailable, plus details.app_reason and the receiver's text in message |
| 403 | app_unavailable | The app is read-only and can't send messages |
| 404 | not_found | The room doesn't exist or was dissolved; the from user doesn't exist |
| 413 | payload_too_large | The message is over the app's size limit; details.max_bytes is the limit |
| 429 | rate_limited | details.reason room_send_rate: over the room's or app's per-second message quota |
| 500 | internal | details.reason dependency_unavailable: the message couldn't be pushed and wasn't sent. Wait for the Retry-After header, then retry with the same client_msg_id |
Query recent messages
Return a room's recent messages, oldest first, without requiring anyone to be in the room. Only normal and high-priority messages, excluding low-priority and recalled ones, kept for at most 24 hours; how many are kept depends on chatroom_history_size in the runtime policy (50 by default), see Recent messages. This isn't full history and can't be paged back.
/{org_name}/{app_name}/chatrooms/{room_id}/messagesPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
room_id | String | The chat room ID |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Number | No | Return at most this many of the latest messages, a positive integer. The default and maximum are both chatroom_history_size; larger values return that many |
A response holds at most 1 MB of messages; beyond that the oldest ones are left out.
Request example
curl -X GET "$IM_API/$ORG/$APP/chatrooms/100310941534519296/messages?limit=3" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK, with each item in items a chat room message object, oldest first; empty if there are none.
{
"items": [
{
"room_id": "100310941534519296",
"message_id": "100312466755420160",
"client_msg_id": "991d51c8-0e88-43d1-a830-ee3f160075cb",
"sender": "zhaoliu",
"sender_type": "user",
"sender_nickname": "赵六",
"sender_avatar_url": "https://cdn.example.com/avatar/zhaoliu.png",
"sender_role": "member",
"type": "text",
"body": { "text": "感谢大家的支持" },
"ext": null,
"priority": "normal",
"via": "openapi",
"created_at": "2026-10-04T19:25:55.790Z"
},
{
"room_id": "100310941534519296",
"message_id": "100312466948358144",
"client_msg_id": "gift-8f3a91",
"sender": "zhangsan",
"sender_type": "user",
"sender_nickname": "张三",
"sender_avatar_url": "https://cdn.example.com/avatar/zhangsan.png",
"sender_role": "owner",
"type": "custom",
"body": { "event": "gift", "gift_id": "rocket", "count": 1 },
"ext": { "combo": 3 },
"priority": "high",
"via": "openapi",
"created_at": "2026-10-04T19:25:55.836Z"
},
{
"room_id": "100310941534519296",
"message_id": "100312501224210432",
"client_msg_id": "c-97ff80f040e8",
"sender": "sunqi",
"sender_type": "user",
"sender_nickname": "孙七",
"sender_avatar_url": "https://cdn.example.com/avatar/sunqi.png",
"sender_role": "member",
"type": "text",
"body": { "text": "唱得太好了!" },
"ext": null,
"priority": "normal",
"via": "client",
"created_at": "2026-10-04T19:26:04.008Z"
}
]
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | limit isn't a positive integer |
| 404 | not_found | The room doesn't exist or was dissolved |
| 500 | internal | details.reason dependency_unavailable: temporarily unreadable; wait for the Retry-After header and retry |
Recall a chat room message
Recall a message in a chat room, regardless of sender or send time. Afterwards it's removed from recent messages, and online members get a recall notice in real time (recalled_by null, role server), so clients remove it from the screen.
A recall can't guarantee nobody saw it
The message was already pushed to online members; recalling only tells clients to stop showing it.
Messages not in recent messages: low-priority messages, messages beyond the kept count or 24 hours, and messages already recalled aren't in recent messages. The server can't confirm whether they exist when recalling them, so it still pushes the recall notice and returns changed true; clients ignore message IDs they don't know. So recalling the same message again also pushes another notice, again with changed true.
Banned rooms can recall too.
/{org_name}/{app_name}/chatrooms/{room_id}/messages/{message_id}/recallPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
room_id | String | The chat room ID |
message_id | String | The message to recall |
Request body
The body is optional.
| Field | Type | Required | Description |
|---|---|---|---|
reason | String | No | Why it was recalled, up to 256 characters without control characters. Only recorded in the console's operation log and never shown to users |
Request example
curl -X POST "$IM_API/$ORG/$APP/chatrooms/100310941534519296/messages/100312501224210432/recall" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "含有违规内容"
}'Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
changed | Boolean | Whether a recall notice was pushed. false only in rare cases (several requests recalling the same message at once) |
{
"changed": true
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | reason is longer than 256 characters or has control characters |
| 404 | not_found | The room doesn't exist or was dissolved; message_id is malformed |
| 500 | internal | details.reason dependency_unavailable: temporarily unable to recall; wait for the Retry-After header and retry |
Rules for server-sent messages
When users send chat room messages from clients:
- The connection sending the message must be in the room.
- They must not be muted in the room; during mute-all they must be the owner, an admin or on the allowlist.
- They must not be globally muted.
- Only the owner and admins can send high-priority messages.
- Each user can send at most 5 per second (bursts of 10).
When your server sends as a user or as the system, none of the above is checked: any sender can send high-priority messages, and attachment addresses aren't checked against media_url_only. The server only checks:
| Check | If it fails |
|---|---|
| The type, content and size are valid | 400 invalid_argument, 413 payload_too_large |
The sender (from) exists and isn't deleted | 404 not_found |
| The sender isn't banned by the platform (users banned by your app can still be senders) | 403 user_disabled |
| The room exists and isn't dissolved | 404 not_found |
| The room isn't banned (including by you) | 403 chatroom_disabled |
| The app isn't read-only | 403 app_unavailable |
| Per-room and per-app sending rate limits | 429 rate_limited for normal and high priority; low-priority messages are dropped |
Rate limits count client and server messages together; see Message priority and delivery for the rules:
| Limit | Value | Over the limit |
|---|---|---|
| Normal and low-priority messages per room per second | chatroom_msg_per_second in the runtime policy, 40 by default, 1 to 200 | Normal messages get 429 rate_limited with details.reason room_send_rate; low-priority messages are dropped, with dropped true in the response |
| High-priority messages per room per second | 20 | 429 rate_limited with details.reason room_send_rate |
| Messages per second across all the app's rooms | The chatroom_msg_per_sec_limit quota, 2000 by default, counting all three priorities | As above, also details.reason room_send_rate |
Chat room messages don't count toward the one-to-one and group message quota msg_per_min_limit. Send requests count toward the app's calls per second like any other OpenAPI request.
{
"error": {
"code": "rate_limited",
"message": "聊天室消息太多,请稍后再试",
"details": { "reason": "room_send_rate", "retry_after_seconds": 1 },
"request_id": "KK4Z3DWPMCYBGRI7SYJK3OQQOL"
}
}Pre-send checks: after the checks above, messages go through two more checks in turn, 500 milliseconds in total at most:
| Check | Rules | If it fails |
|---|---|---|
| Moderation | Server-sent messages are checked against platform rules only by default; with check server-submitted content too enabled for chat room messages, also against your word lists and third-party review. At most 200 milliseconds; third-party timeouts or errors pass and are reviewed later; low-priority messages only use word lists | 403 message_rejected (sensitive_content and so on, see Errors callers see); text matching replacement rules is sent with the replacement |
| Before-send-chat-room-message callback | Called only for client messages by default; also for server messages when its vias include openapi. Waits for the response within the remaining time | A rejection returns 403 message_rejected (app_rejected); a timeout or failure under the "reject" policy returns the same (callback_unavailable); rewritten content is sent with the rewritten body and ext, see Errors the caller receives |
body and ext in the response are what was actually sent after replacement and rewriting. When a message a user sent from a client is rewritten, the reply to chatroom.send on the connection also carries the rewritten body and ext, so the client can update its local copy.
Data structures
Chat room message object
Messages pushed to clients, send responses and recent messages all share this format.
| Field | Type | Description |
|---|---|---|
room_id | String | The chat room ID |
message_id | String | The message ID, generated by the server |
client_msg_id | String | The sender's client_msg_id; a server-generated UUID when your server didn't give one; may be null for low-priority messages sent from clients |
sender | String | The sender's username; null for system messages |
sender_type | String | user or system |
sender_nickname | String | The sender's nickname at send time, not updated if their profile changes later; null for system messages |
sender_avatar_url | String | The sender's avatar address at send time; null for system messages |
sender_role | String | The sender's room role at send time: owner, admin or member; null for system messages |
type | String | Message type, see Message types |
body | Object | The message body |
ext | Object | Extension fields; null if none |
priority | String | high, normal or low |
via | String | Where it was sent from: client, openapi your server, console, or system sent automatically by this service |
created_at | String | Send time. Messages in a room are only roughly ordered by time; clients can just show them in arrival order |
{
"room_id": "100310941534519296",
"message_id": "100312324354605056",
"client_msg_id": "b1f3c0de-5e7a-4f55-9c1d-2a8e6f0b9d11",
"sender": "lisi",
"sender_type": "user",
"sender_nickname": "李四",
"sender_avatar_url": "https://cdn.example.com/avatar/lisi.png",
"sender_role": "admin",
"type": "text",
"body": { "text": "欢迎大家,开播后请文明发言" },
"ext": null,
"priority": "normal",
"via": "openapi",
"created_at": "2026-10-04T19:25:21.839Z"
}