会话与历史消息
会话是消息所在的地方:两个用户之间的单聊只有一个会话,每个群有一个群会话(会话 ID 就是群 ID),见会话 ID。会话在写入第一条消息时创建:单聊在两人之间第一次发消息时,群会话在建群时(建群的群提示就是第一条消息)。
本页的接口从两个角度查询会话:
- 服务端视角:会话对象和会话中的历史消息。看到的是会话中的全部消息,不受任何用户删除消息、清空聊天记录的影响,也不受群成员入群时间的限制,用于归档、客服查看聊天记录、处理投诉等;
- 某个用户的视角:用户会话对象,即这个用户在会话中的个人状态,如已读位置、未读数、是否置顶、是否已删除会话、在群里的身份。每个用户的状态只影响他自己,同一个会话中各人的状态互不相同。
消息序号
每条消息在会话中有一个序号 seq,从 1 开始连续递增,单聊双方看到的序号相同。历史消息按序号翻页;已读位置、清空聊天记录的位置也都用序号表示。
超过消息保留期(默认 90 天)的消息,本页的接口都不再返回;会话本身、用户的会话状态不随消息删除。
查询会话信息
按会话 ID 查询会话的基本信息,单聊和群聊都可以。
/{org_name}/{app_name}/conversations/{conversation_id}路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
conversation_id | String | 会话 ID;群会话的 ID 就是群 ID |
请求示例
curl "$IM_API/$ORG/$APP/conversations/99582660229201920" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,响应体为会话对象。
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"users": ["alice", "bob"],
"group_id": null,
"max_seq": 20,
"expired_seq": 0,
"message_version": 0,
"last_message_at": "2026-10-02T19:14:53.302Z",
"status": "active",
"created_at": "2026-10-02T19:05:56.355Z"
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 会话不存在,或 conversation_id 不是有效的 ID |
查询两个用户之间的单聊会话
按两个用户名查询他们之间的单聊会话,返回会话对象。得到 conversation_id 后,可以用它拉取历史消息。
/{org_name}/{app_name}/single-conversations/{username}/{peer}路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 一方的用户名,不区分大小写 |
peer | String | 另一方的用户名,不区分大小写。两个用户名的顺序不影响结果 |
请求示例
curl "$IM_API/$ORG/$APP/single-conversations/bob/alice" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,响应体为会话对象。users 的顺序是固定的,与路径中两个用户名的顺序无关。
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"users": ["alice", "bob"],
"group_id": null,
"max_seq": 3,
"expired_seq": 0,
"message_version": 0,
"last_message_at": "2026-10-02T19:05:56.454Z",
"status": "active",
"created_at": "2026-10-02T19:05:56.355Z"
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | message 为“用户不存在”:任一用户不存在或已删除 |
| 404 | not_found | message 为“会话不存在”:两人之间还没有发过消息,或 username 与 peer 是同一个用户 |
拉取会话的历史消息
按序号或时间段拉取会话中的消息。这是服务端视角:返回会话中的全部消息,包括被某个用户删除、清空的消息,以及群成员入群之前的消息。已撤回的消息仍会返回,但不含内容,见消息对象。
/{org_name}/{app_name}/conversations/{conversation_id}/messages路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
conversation_id | String | 会话 ID |
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
before_seq | Number | 否 | 返回序号小于它的消息,用于向前(更早)翻页 |
after_seq | Number | 否 | 返回序号大于它的消息,用于向后(更新)翻页。传 0 表示从第一条开始 |
around_seq | Number | 否 | 返回这条消息本身和它前后的消息,用于从某条消息(如搜索结果)开始查看上下文 |
start_time | String | 否 | 只返回时间不早于它的消息,RFC 3339 格式,如 2026-10-02T19:07:00Z |
end_time | String | 否 | 只返回时间早于它的消息,RFC 3339 格式 |
limit | Number | 否 | 最多返回的条数,默认 20,取值 1 到 100 |
before_seq、after_seq、around_seq最多给一个,都不给时返回最新的一页。序号参数必须是非负整数。start_time、end_time可以与序号参数同时使用,用来限定时间段,见下文按时间段拉取。
翻页方式
无论哪种方式,items 中的消息都按序号从小到大排列。
| 请求 | 返回 | 继续翻页 |
|---|---|---|
| 不给序号参数 | 最新的 limit 条 | 把响应中的 next_before_seq 作为 before_seq,向前翻页 |
before_seq | 序号小于它的、最接近它的 limit 条 | 同上;has_more 为 false 时已经到了最早的消息 |
after_seq | 序号大于它的、最接近它的 limit 条 | 把响应中的 next_after_seq 作为 after_seq,向后翻页;has_more 为 false 时已经到了最新的消息 |
around_seq | 它本身、它之前的 limit / 2 条(向下取整)和之后的其余条数 | 分别用 next_before_seq、next_after_seq 向前、向后翻页,has_more_before、has_more_after 表示两个方向是否还有 |
一页的条数可能少于 limit(例如跳过了超过保留期的消息,或一页的消息合计超过 1 MB 时提前结束),请以 has_more(around_seq 时为 has_more_before、has_more_after)判断是否还有更多,不要用条数判断。
按时间段拉取
- 给了
start_time:从时间段的起点开始,按序号从小到大返回,响应带next_after_seq和has_more。翻页时保留start_time、end_time,另外带上after_seq(上一页的next_after_seq)。 - 只给了
end_time:返回end_time之前最新的一页,用法与不给序号参数相同,翻页时保留end_time,另外带上before_seq。 start_time包含在时间段内,end_time不包含。时间可以带时区,如2026-10-03T03:07:00+08:00。
例如导出某个单聊在 10 月 2 日(UTC)的全部消息:
# 第一页
curl "$IM_API/$ORG/$APP/conversations/99582660229201920/messages?start_time=2026-10-02T00:00:00Z&end_time=2026-10-03T00:00:00Z&limit=100" \
-H "Authorization: Bearer $APP_TOKEN"
# 之后的页:带上上一页的 next_after_seq,直到 has_more 为 false
curl "$IM_API/$ORG/$APP/conversations/99582660229201920/messages?start_time=2026-10-02T00:00:00Z&end_time=2026-10-03T00:00:00Z&limit=100&after_seq=100" \
-H "Authorization: Bearer $APP_TOKEN"请求示例
向前翻页,取序号小于 4 的 2 条消息:
curl "$IM_API/$ORG/$APP/conversations/99582660229201920/messages?before_seq=4&limit=2" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK:
| 字段 | 类型 | 说明 |
|---|---|---|
items | Array<Object> | 消息对象,按序号从小到大。没有消息时为空数组 |
next_before_seq | Number | 给 before_seq 或 around_seq,或者序号参数和 start_time 都不给时返回。这一页扫描到的最小序号,下一页用它作为 before_seq |
next_after_seq | Number | 给 after_seq 或 around_seq,或者给了 start_time 而不给序号参数时返回。这一页扫描到的最大序号,下一页用它作为 after_seq |
has_more | Boolean | 这个方向是否还有更多消息。around_seq 时不返回,改为下面两个字段 |
has_more_before | Boolean | 只在给 around_seq 时返回:更早的方向是否还有 |
has_more_after | Boolean | 只在给 around_seq 时返回:更新的方向是否还有 |
{
"has_more": true,
"items": [
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"seq": 2,
"message_id": "99582660455694336",
"client_msg_id": "98a76014c1d7236246907d80fd19133e",
"sender": "bob",
"sender_type": "user",
"recipient": "alice",
"type": "text",
"body": { "text": "你好 Alice" },
"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:05:56.408Z",
"message_version": 0
},
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"seq": 3,
"message_id": "99582660644438016",
"client_msg_id": "3a72c5d2992928f8ab91d233028878ee",
"sender": "alice",
"sender_type": "user",
"recipient": "bob",
"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:05:56.454Z",
"message_version": 0
}
],
"next_before_seq": 2
}用 around_seq=5&limit=3 拉取第 5 条消息前后的消息时,items 为第 4、5、6 条,翻页字段为 next_before_seq: 4、has_more_before: true、next_after_seq: 6、has_more_after: true,之后用 before_seq=4 和 after_seq=6 分别向两个方向继续翻页。
超出范围的请求(如 after_seq 不小于最新消息的序号、before_seq 为 1)返回空的 items,has_more 为 false。
错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | before_seq、after_seq、around_seq 给了多个,details.reason 为 conflicting_params |
| 400 | invalid_argument | 序号参数不是非负整数,limit 不在 1 到 100 之间,或 start_time、end_time 不是 RFC 3339 格式 |
| 404 | not_found | 会话不存在 |
查询用户的会话列表
列出一个用户的全部会话状态:他有过消息的单聊、所在的群,以及已经离开但还能查看历史消息的群。每一项是用户会话对象,带有未读数、已读位置、置顶和删除状态。
/{org_name}/{app_name}/users/{username}/conversations- 排序:按会话 ID 从小到大排列,不是按最后一条消息的时间或置顶排序。需要像客户端那样显示会话列表时,取完全部页后按
pinned_at(置顶的在前)和last_message_at自行排序。 - 包含被删除的会话:用户删除过的会话仍在列表中,
hidden为true,显示时请跳过它们。已离开的群也在列表中,membership为left、removed或dismissed。 - 用户刚加入的群,要在入群处理完成后(通常在几秒内)才出现在列表中;在此之前可以用查询用户的单个会话按群 ID 查询。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名,不区分大小写 |
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
cursor | String | 否 | 分页游标,第一页不传,见分页 |
limit | Number | 否 | 每页条数,默认 20,取值 1 到 100 |
请求示例
curl "$IM_API/$ORG/$APP/users/bob/conversations?limit=2" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK:items 为用户会话对象的数组,next_cursor 为下一页的游标,没有下一页时为 null。最后一页正好满 limit 条时,next_cursor 也可能不为 null,用它取到的下一页为空。
{
"items": [
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"peer": "alice",
"group_id": null,
"membership": null,
"start_seq": 1,
"max_seq": 11,
"read_seq": 11,
"unread_count": 0,
"marked_unread": true,
"peer_read_seq": 10,
"mention_seq": null,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": null,
"joined_at": null,
"left_at": null,
"message_version": 0,
"last_message_at": "2026-10-02T19:09:19.677Z",
"version": 18
},
{
"conversation_id": "99582677887221760",
"conversation_type": "group",
"peer": null,
"group_id": "99582677887221760",
"membership": "member",
"start_seq": 1,
"max_seq": 5,
"read_seq": 0,
"unread_count": 3,
"marked_unread": false,
"peer_read_seq": null,
"mention_seq": null,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": "2026-10-02T19:09:44.467Z",
"joined_at": "2026-10-02T19:06:00.566Z",
"left_at": null,
"message_version": 0,
"last_message_at": "2026-10-02T19:06:06.310Z",
"version": 17
}
],
"next_cursor": "99582677887221760"
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | cursor 无效,或 limit 不在 1 到 100 之间 |
| 404 | not_found | 用户不存在或已删除 |
查询用户的单个会话
查询一个用户在某个会话中的状态,返回用户会话对象,如他在这个会话中的未读数、已读位置、是否被 @、是否置顶或删除了会话。
/{org_name}/{app_name}/users/{username}/conversations/{conversation_id}用户必须能访问这个会话:单聊中是会话的一方;群聊中是当前成员,或者曾经是成员(已退出、被移出或群已解散,仍能查看离开之前的消息)。
路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名,不区分大小写 |
conversation_id | String | 会话 ID;群会话的 ID 就是群 ID |
请求示例
curl "$IM_API/$ORG/$APP/users/carol/conversations/99582677887221760" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK,响应体为用户会话对象。下例中 carol 有 3 条未读消息,其中第 5 条 @ 了她:
{
"conversation_id": "99582677887221760",
"conversation_type": "group",
"peer": null,
"group_id": "99582677887221760",
"membership": "member",
"start_seq": 1,
"max_seq": 5,
"read_seq": 0,
"unread_count": 3,
"marked_unread": false,
"peer_read_seq": null,
"mention_seq": 5,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": null,
"joined_at": "2026-10-02T19:06:00.566Z",
"left_at": null,
"message_version": 0,
"last_message_at": "2026-10-02T19:06:06.310Z",
"version": 1
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 用户不存在或已删除;会话不存在;单聊中用户不是会话的一方;群不存在,或者是私有群而用户从未入群 |
| 403 | not_group_member | 公开群,用户不是成员,也没有离开之前的记录 |
| 403 | not_group_member | details.reason 为 leave_pending:用户刚离开群,离开的处理还没完成,稍后重试即可得到 membership 为已离开的状态 |
数据结构
会话对象
服务端视角的会话信息,由查询会话信息和查询两个用户之间的单聊会话返回。
| 字段 | 类型 | 说明 |
|---|---|---|
conversation_id | String | 会话 ID;群会话的 ID 就是群 ID |
conversation_type | String | single 单聊 / group 群聊 |
users | Array<String> | 单聊的两个用户名,顺序固定;已删除的用户为 null。群聊为 null |
group_id | String | 群聊的群 ID;单聊为 null |
max_seq | Number | 会话中最后一条消息的序号 |
expired_seq | Number | 序号不大于它的消息已超过保留期被删除,0 表示没有 |
message_version | Number | 会话中已有消息的变更版本号:消息被撤回、编辑、擦除、表情回应或置顶时加一 |
last_message_at | String | 最后一条消息的时间,可为 null |
status | String | active 正常 / ended 已结束(群已解散,不再写入新消息) |
created_at | String | 会话创建的时间,即第一条消息写入时 |
{
"conversation_id": "99582864340811776",
"conversation_type": "group",
"users": null,
"group_id": "99582864340811776",
"max_seq": 3,
"expired_seq": 0,
"message_version": 0,
"last_message_at": "2026-10-02T19:14:07.880Z",
"status": "ended",
"created_at": "2026-10-02T19:06:45.376Z"
}用户会话对象
某个用户在一个会话中的个人状态,由查询用户的会话列表、查询用户的单个会话,以及标记已读、删除会话等操作返回。其中的序号、未读数都按这个用户能看到的范围计算。
| 字段 | 类型 | 说明 |
|---|---|---|
conversation_id | String | 会话 ID |
conversation_type | String | single 单聊 / group 群聊 |
peer | String | 单聊的对方用户名,对方已被删除时为 null;群聊为 null |
group_id | String | 群聊的群 ID;单聊为 null |
membership | String | 群聊中这个用户的身份:member 成员 / left 已退出 / removed 已被移出 / dismissed 群已解散。单聊为 null |
start_seq | Number | 用户能看到的第一条消息的序号。群聊从用户本次入群开始;清空聊天记录后从清空位置的下一条开始;已计入过期删除的消息。大于 max_seq 时表示没有可见的消息 |
max_seq | Number | 用户能看到的最后一条消息的序号。已离开群的,为离开时能看到的最后一条(通常是离开的群提示) |
read_seq | Number | 已读位置:序号不大于它的消息都已读。只增不减 |
unread_count | Number | 未读数,计算规则见未读数的计算 |
marked_unread | Boolean | 用户是否在客户端把会话标记为未读(未读数为 0 时客户端显示一个未读的圆点)。标记已读、清空聊天记录、删除会话时自动取消 |
peer_read_seq | Number | 单聊中对方已读到的序号,用于显示“已读”;应用关闭了单聊已读回执时为 null。群聊为 null |
mention_seq | Number | 用户还没读到的、@ 了他或 @ 全体成员的最近一条消息的序号,用于显示“有人 @ 我”;没有时为 null |
cleared_seq | Number | 清空聊天记录的位置,用户看不到序号不大于它的消息;0 表示没有清空过 |
hidden_seq | Number | 删除会话时的位置;0 表示没有删除过 |
hidden | Boolean | 会话是否处于删除状态。为 true 时不应显示在会话列表中;之后有新消息时变为 false,见删除会话 |
pinned_at | String | 用户置顶这个会话的时间,没有置顶为 null。置顶由用户在客户端设置,每人最多 100 个;删除会话时自动取消 |
joined_at | String | 群聊中用户本次入群的时间;单聊为 null |
left_at | String | 群聊中用户离开的时间,仍是成员时为 null;单聊为 null |
message_version | Number | 会话当前的消息变更版本号,同会话对象中的 message_version |
last_message_at | String | 用户能看到的最后一条消息的时间,可为 null |
version | Number | 这个会话的状态最近一次变化时,用户会话列表的版本号。客户端 SDK 用它增量同步,服务端通常不需要使用;群会话中还没有写入过用户的状态时为 0 |
单聊的例子(bob 的视角:会话共 10 条消息,他读到了第 5 条,还有 5 条未读;对方 alice 还没有标记过已读):
{
"conversation_id": "99582660229201920",
"conversation_type": "single",
"peer": "alice",
"group_id": null,
"membership": null,
"start_seq": 1,
"max_seq": 10,
"read_seq": 5,
"unread_count": 5,
"marked_unread": false,
"peer_read_seq": 0,
"mention_seq": null,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": null,
"joined_at": null,
"left_at": null,
"message_version": 0,
"last_message_at": "2026-10-02T19:07:06.211Z",
"version": 12
}已被移出的群(只能看到第 7 到 9 条,第 9 条是移出的群提示):
{
"conversation_id": "99582677887221760",
"conversation_type": "group",
"peer": null,
"group_id": "99582677887221760",
"membership": "removed",
"start_seq": 7,
"max_seq": 9,
"read_seq": 6,
"unread_count": 1,
"marked_unread": false,
"peer_read_seq": null,
"mention_seq": null,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": null,
"joined_at": "2026-10-02T19:12:43.955Z",
"left_at": "2026-10-02T19:13:17.866Z",
"message_version": 0,
"last_message_at": "2026-10-02T19:13:18.378Z",
"version": 1
}