會話與歷史訊息
從你的服務端查詢會話和聊天記錄,常用於歸檔、客服檢視聊天記錄、處理投訴。
| 要做什麼 | 介面 |
|---|---|
| 查 alice 和 bob 之間的聊天記錄 | 先查詢兩個使用者之間的單聊會話得到 conversation_id,再拉取會話的歷史訊息 |
| 查一個群的聊天記錄 | 群 ID 就是會話 ID,直接拉取會話的歷史訊息 |
| 匯出某天的聊天記錄 | 按時間段拉取 |
| 查使用者的會話列表、未讀數 | 查詢使用者的會話列表、查詢使用者的單個會話 |
例如,查 alice 和 bob 之間最新的 20 條訊息:
CONV=$(curl -s "$IM_API/$ORG/$APP/single-conversations/alice/bob" \
-H "Authorization: Bearer $APP_TOKEN" | jq -r .conversation_id)
curl "$IM_API/$ORG/$APP/conversations/$CONV/messages?limit=20" \
-H "Authorization: Bearer $APP_TOKEN"本頁的介面有兩種視角:
| 視角 | 返回 | 看到的訊息 |
|---|---|---|
| 服務端 | 會話物件和歷史訊息 | 會話中的全部訊息,不受使用者刪除訊息、清空聊天記錄或入群時間的影響 |
| 某個使用者 | 使用者會話物件:他的已讀位置、未讀數、置頂、是否刪除了會話、在群裡的身份 | 只有他能看到的範圍;每個人的狀態互不影響 |
幾個基本事實:
- 兩人之間的單聊只有一個會話,在第一次發訊息時建立;群會話在建群時建立,會話 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(寫在 URL 裡時 + 要編碼為 %2B)。
例如匯出某個單聊在 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) | 顯示時跳過 hidden 為 true 的 |
| 剛加入的群要等入群處理完成(通常幾秒內)才出現 | 在此之前用查詢使用者的單個會話按群 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
}