撤回、编辑与置顶
你的业务服务端可以撤回任何消息、编辑文本消息和自定义消息的内容、置顶或取消置顶会话中的消息。这些操作都按消息 ID(message_id)指定消息,修改后会话中能看到这条消息的用户都会同步到变化:在线的设备立即收到通知,离线的设备在下次同步时得知。
这些接口都返回修改后的消息对象和 changed。重复操作(撤回已撤回的消息、置顶已置顶的消息、编辑为相同的内容)不报错,changed 为 false,消息保持不变,可以放心重试。
撤回消息
撤回一条消息。服务端可以撤回任何人发的任何消息,包括以系统身份发送的消息和群提示,不受撤回时限的限制:运行策略中的 recall_window_seconds(默认 120 秒)只限制用户在客户端撤回自己的消息。
/{org_name}/{app_name}/messages/{message_id}/recall撤回之后:
- 消息的内容被清除,
body、ext、mentions、reply_to都变为null,recalled为{ "by": null, "role": "server", "at": "..." }。撤回的内容不能恢复,服务端也无法再查到,需要留档的请在撤回前导出; - 消息上的表情回应随之清除,置顶随之取消;被 @ 的人的“有人 @ 我”提醒退回到之前一条仍然有效的 @;未读数不变;
- 会话的参与者(包括发送者,以及已经离开群、但能看到这条消息的人)都会同步到撤回。客户端用撤回提示替换原消息,可以根据
recalled.role显示不同的文字,如server时显示“该消息已被撤回”; - 接收者设备上这条消息的离线推送通知被替换为“一条消息已被撤回”(部分推送通道不支持),还没有发出的推送不再发出,见撤回后更新通知;
- 引用了这条消息的消息仍保留引用的序号,客户端显示“原消息已撤回”;之后也不能再引用、编辑或置顶它;
- 消息中的附件文件不会随之删除,在保留期内仍可以凭文件地址换取下载地址。违规的图片或文件需要彻底删除时,在撤回前记下文件地址,再调用删除文件。
应用处于只读状态时也可以撤回。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
message_id | String | 要撤回的消息 ID |
请求体
可以没有请求体。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | String | 否 | 撤回的原因,记入操作日志,不会通知给用户 |
请求示例
curl -X POST "$IM_API/$ORG/$APP/messages/99583385822822400/recall" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "违规内容"
}'响应
成功返回 200 OK:
| 字段 | 类型 | 说明 |
|---|---|---|
message | Object | 撤回后的消息对象 |
changed | Boolean | 这次是否撤回了消息;消息之前已经被撤回时为 false |
{
"message": {
"conversation_id": "99583031936811008",
"conversation_type": "single",
"seq": 3,
"message_id": "99583385822822400",
"client_msg_id": "d83eb893f17d57d202db27cb79f46ef0",
"sender": "carol",
"sender_type": "user",
"recipient": "dave",
"type": "text",
"body": null,
"ext": null,
"mentions": null,
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": null,
"edited": null,
"recalled": { "by": null, "role": "server", "at": "2026-10-02T19:08:59.798Z" },
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:08:49.350Z",
"message_version": 22
},
"changed": true
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 消息不存在、已超过保留期,或 message_id 的格式不对 |
批量撤回消息
一次撤回多条消息,最多 100 条,例如撤回某个用户刷屏的消息(可以先用查询用户发出的消息找出它们)。每条消息分别处理,规则与撤回消息相同,某条失败不影响其他条。
/{org_name}/{app_name}/messages/batch-recall调用一次按消息条数计入应用每秒的调用次数。
路径参数
org_name、app_name 见接入概述。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
message_ids | Array<String> | 是 | 要撤回的消息 ID,1 到 100 个,每个都必须是字符串 |
reason | String | 否 | 撤回的原因,记入操作日志 |
请求示例
curl -X POST "$IM_API/$ORG/$APP/messages/batch-recall" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message_ids": ["99583386074480640", "99583385822822400", "123", "99583386288390144"],
"reason": "批量清理"
}'响应
成功返回 200 OK,results 中按 message_ids 的顺序列出每条消息的结果:
| 字段 | 类型 | 说明 |
|---|---|---|
message_id | String | 消息 ID |
changed | Boolean | 成功时出现:这次是否撤回了消息,之前已经撤回的为 false |
code | String | 失败时出现:错误码,如 not_found |
message | String | 失败时出现:错误说明 |
details | Object | 失败时出现:详细信息,可为 null |
{
"results": [
{ "message_id": "99583386074480640", "changed": true },
{ "message_id": "99583385822822400", "changed": false },
{ "message_id": "123", "code": "not_found", "message": "消息不存在", "details": null },
{ "message_id": "99583386288390144", "changed": true }
]
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | message_ids 为空或超过 100 个(details.reason 为 too_many_items,details.max 为 100),或其中有不是字符串形式整数的 ID |
编辑消息
修改一条消息的内容,用于更新订单、审批这类卡片的状态,或更正服务端发出的通知。只能编辑文本消息(text)和自定义消息(custom),不论它是谁、通过什么方式发送的;其他类型和群提示不能编辑。
/{org_name}/{app_name}/messages/{message_id}body和ext至少给出一个,给出的字段整体替换原来的值,没有给出的保持不变;ext为null时清空扩展字段;- 新内容按发送时的规则校验类型、格式和大小上限;
mentions、reply_to、need_receipt不能修改; - 新内容同样经过内容安全检查(默认只按平台规则,见服务端提交的内容):不通过时返回
403 message_rejected,消息不变;命中替换规则的文字被替换为*,以响应中的消息为准; - 发消息前回调的调用条件开启了“包括编辑”(
include_edits),且vias包含openapi时,编辑也会调用,data.edit为被编辑消息的message_id和seq:被拒绝时返回403 message_rejected,消息不变;被改写时保存改写后的内容; - 每条消息最多编辑 100 次,没有时间限制。新内容与当前内容完全相同时不算一次编辑,
changed为false; - 编辑后
edited记下最近一次编辑的时间和累计次数,客户端通常显示“已编辑”;会话的参与者都会同步到新的内容。
应用处于只读状态时不能编辑,返回 403 app_unavailable。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
message_id | String | 要编辑的消息 ID |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
body | Object | 否 | 新的消息体,整体替换原来的 body,格式见消息类型 |
ext | Object | 否 | 新的扩展字段,整体替换原来的 ext;为 null 时清空 |
reason | String | 否 | 编辑的原因,记入操作日志 |
请求示例
curl -X PATCH "$IM_API/$ORG/$APP/messages/99582642919309312" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": { "card": "order_shipped", "order_id": "8812", "status": "delivered" },
"reason": "订单状态更新"
}'响应
成功返回 200 OK,message 为编辑后的消息对象,changed 表示内容是否有变化:
{
"message": {
"conversation_id": "99582604130385920",
"conversation_type": "single",
"seq": 7,
"message_id": "99582642919309312",
"client_msg_id": "2ec322dc5f952fd2d05bc6df94a44d9a",
"sender": "bob",
"sender_type": "user",
"recipient": "alice",
"type": "custom",
"body": { "card": "order_shipped", "order_id": "8812", "status": "delivered" },
"ext": { "source": "order-system" },
"mentions": null,
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": null,
"edited": { "at": "2026-10-02T19:08:27.350Z", "count": 1 },
"recalled": null,
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:05:52.228Z",
"message_version": 1
},
"changed": true
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | body 和 ext 都没有给出(details.reason 为 empty_edit);新内容不符合要求(invalid_body,details.field 指出字段) |
| 403 | permission_denied | details.reason 为 not_editable:不是文本消息或自定义消息;message_recalled:消息已被撤回或擦除 |
| 403 | message_rejected | 新内容未通过内容安全检查,details.reason 为 sensitive_content、provider_block 或 check_timeout,见调用方看到的错误;或被发消息前回调拒绝,details.reason 为 app_rejected 或 callback_unavailable,另有 details.app_reason,见操作方收到的错误 |
| 404 | not_found | 消息不存在或已超过保留期 |
| 409 | limit_exceeded | 这条消息已编辑 100 次,details.reason 为 edit_limit |
| 413 | payload_too_large | 新内容超过应用的大小上限,details.max_bytes 为上限 |
置顶消息
把一条消息置顶在会话顶部,会话的全部参与者可见,适合公告、重要通知。服务端置顶不检查角色(客户端只有单聊双方、群主和群管理员可以置顶),消息的 pinned.by 为 null。
/{org_name}/{app_name}/messages/{message_id}/pin- 每个会话最多置顶应用运行策略中
max_pinned_messages_per_conversation条消息,默认 20,可设 1 到 100,见运行策略。已满时请先取消置顶一条; - 已撤回、已擦除的消息不能置顶;已置顶的消息被撤回、擦除或超过保留期时,自动移出置顶;
- 群里新加入的成员看不到入群之前的消息,也就看不到这些消息的置顶。
应用处于只读状态时不能置顶,返回 403 app_unavailable。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
message_id | String | 要置顶的消息 ID |
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | String | 否 | 置顶的原因,记入操作日志 |
请求示例
curl -X PUT "$IM_API/$ORG/$APP/messages/99582672556261376/pin" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,message 为置顶后的消息对象,changed 为这次是否置顶了消息(已经置顶的为 false):
{
"message": {
"conversation_id": "99582580415791104",
"conversation_type": "group",
"seq": 3,
"message_id": "99582672556261376",
"client_msg_id": "doc-grp-0001",
"sender": "alice",
"sender_type": "user",
"recipient": null,
"type": "text",
"body": { "text": "@bob 请看一下需求文档" },
"ext": null,
"mentions": { "usernames": ["bob"], "all": false },
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": { "by": null, "at": "2026-10-02T19:08:36.382Z" },
"edited": null,
"recalled": null,
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:05:59.294Z",
"message_version": 1
},
"changed": true
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 403 | permission_denied | 消息已被撤回或擦除,details.reason 为 message_recalled |
| 404 | not_found | 消息不存在或已超过保留期 |
| 409 | limit_exceeded | 会话中置顶的消息已达上限,details.reason 为 pinned_message_limit |
取消置顶消息
取消一条消息的置顶,不论它是谁置顶的。消息没有置顶时 changed 为 false。应用处于只读状态时也可以取消置顶。
/{org_name}/{app_name}/messages/{message_id}/pin路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
message_id | String | 要取消置顶的消息 ID |
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | String | 否 | 取消置顶的原因,记入操作日志 |
请求示例
curl -X DELETE "$IM_API/$ORG/$APP/messages/99583385617301504/pin" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,格式与置顶消息相同,message.pinned 为 null:
{
"message": {
"conversation_id": "99583031936811008",
"conversation_type": "single",
"seq": 2,
"message_id": "99583385617301504",
"client_msg_id": "7109113ff74e7b0e1e98fc0d29f56f4e",
"sender": "carol",
"sender_type": "user",
"recipient": "dave",
"type": "text",
"body": { "text": "本周值班表已更新" },
"ext": null,
"mentions": null,
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": null,
"edited": null,
"recalled": null,
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:08:49.301Z",
"message_version": 21
},
"changed": true
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 消息不存在或已超过保留期 |
查询会话中的置顶消息
列出一个会话中全部置顶的消息,按置顶时间从新到旧排列。以服务端的视角返回,单聊和群聊都可以查询。
/{org_name}/{app_name}/conversations/{conversation_id}/pins置顶的消息最多 100 条,通常一次返回全部。一次响应中的消息合计超过 1 MB 时分页返回,next_cursor 不为 null 时用它取下一页。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
conversation_id | String | 会话 ID,群聊为群 ID |
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
cursor | String | 否 | 下一页的游标,第一页不传 |
请求示例
curl "$IM_API/$ORG/$APP/conversations/99582580415791104/pins" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,items 为置顶的消息对象,next_cursor 为下一页的游标,没有下一页时为 null:
{
"items": [
{
"conversation_id": "99582580415791104",
"conversation_type": "group",
"seq": 5,
"message_id": "99582672988274688",
"client_msg_id": "doc-grp-0003",
"sender": null,
"sender_type": "system",
"recipient": null,
"type": "text",
"body": { "text": "本群将于今晚 22:00 维护" },
"ext": null,
"mentions": { "usernames": [], "all": true },
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": { "by": null, "at": "2026-10-02T19:08:36.455Z" },
"edited": null,
"recalled": null,
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:05:59.397Z",
"message_version": 2
},
{
"conversation_id": "99582580415791104",
"conversation_type": "group",
"seq": 3,
"message_id": "99582672556261376",
"client_msg_id": "doc-grp-0001",
"sender": "alice",
"sender_type": "user",
"recipient": null,
"type": "text",
"body": { "text": "@bob 请看一下需求文档" },
"ext": null,
"mentions": { "usernames": ["bob"], "all": false },
"reply_to": null,
"need_receipt": false,
"exclude_from_unread": false,
"reactions": null,
"pinned": { "by": null, "at": "2026-10-02T19:08:36.382Z" },
"edited": null,
"recalled": null,
"erased": false,
"via": "openapi",
"created_at": "2026-10-02T19:05:59.294Z",
"message_version": 1
}
],
"next_cursor": null
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | cursor 无效 |
| 404 | not_found | 会话不存在 |
