TwexAPI で XChat DM 履歴を取得する方法
DM history は、どのように取得したか説明できる場合に価値があります。support ticket、CRM sync、partnership log では、conversation ID、message IDs、timestamps、sender/recipient IDs、attachments、どこまで export したかを示す pagination cursor が必要です。
TwexAPI の Get DM History (XChat v3) endpoint は、XChat conversation history を取得します。1:1 workflow では、recipient に user ID、@handle、または 1928096185664843776:1989842918455291904 のような conversation ID を渡せます。
Endpoint and Request Fields
XChat conversation を読むには POST /v3/twitter/dm-history を使います。
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | User ID、@handle、conversation ID a:b、または group ID g...。 |
cookie | string | Yes | X authentication cookie または auth_token。server-side に保存します。 |
pin | string or null | No | Identity PIN。API default は 1234。 |
count | integer or null | No | page size は 10 から 200。default は 50。 |
all | boolean or null | No | true の場合、has_more を辿って全 page を返します。large history では注意。 |
before | string or null | No | manual pagination 用。前 page の最古 message の sequence_id を渡します。 |
proxy | string or null | No | session 用 optional HTTP proxy。 |
Basic Request
curl --request POST \
--url https://api.twexapi.io/v3/twitter/dm-history \
--header "Authorization: Bearer $TWEXAPI_BEARER_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"recipient": "@example_user",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234",
"count": 50,
"all": false
}'response は conversation_id、has_more、messages を返します。
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "conversation_id": "1928096185664843776:1989842918455291904",
6 "has_more": true,
7 "messages": [
8 {
9 "id": "1234567890123456789",
10 "text": "Export が正常に動くことを確認しました。",
11 "time": "2024-01-01T12:00:00Z",
12 "sequence_id": "2076661179334979585",
13 "sender_id": "1928096185664843776",
14 "recipient_id": "1989842918455291904",
15 "attachment": null,
16 "attachments": null
17 }
18 ]
19 }
20}has_more が true の場合、最古 message の sequence_id を取り、次の request の before に渡します。
Python Manual Pagination
manual pagination は all=true より checkpoint を作りやすいです。次の page を取る前に現在 page を保存します。
1import os
2from datetime import datetime, timezone
3from typing import Any
4
5import requests
6
7API_URL = "https://api.twexapi.io/v3/twitter/dm-history"
8BEARER_TOKEN = os.environ["TWEXAPI_BEARER_TOKEN"]
9X_COOKIE = os.environ["TWEXAPI_X_COOKIE"]
10XCHAT_PIN = os.environ.get("TWEXAPI_XCHAT_PIN", "1234")
11PROXY = os.environ.get("TWEXAPI_PROXY")
12
13def fetch_dm_page(
14 recipient: str,
15 *,
16 before: str | None = None,
17 count: int = 50,
18) -> dict[str, Any]:
19 if count < 10 or count > 200:
20 raise ValueError("count must be between 10 and 200")
21
22 payload: dict[str, Any] = {
23 "recipient": recipient,
24 "cookie": X_COOKIE,
25 "pin": XCHAT_PIN,
26 "count": count,
27 "all": False,
28 }
29 if before:
30 payload["before"] = before
31 if PROXY:
32 payload["proxy"] = PROXY
33
34 response = requests.post(
35 API_URL,
36 headers={
37 "Authorization": f"Bearer {BEARER_TOKEN}",
38 "Content-Type": "application/json",
39 "Accept": "application/json",
40 },
41 json=payload,
42 timeout=60,
43 )
44 response.raise_for_status()
45 data = response.json()
46
47 if data.get("code") != 200:
48 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
49
50 return data["data"]
51
52def normalize_message(conversation_id: str, message: dict[str, Any]) -> dict[str, Any]:
53 return {
54 "conversation_id": conversation_id,
55 "message_id": message["id"],
56 "text": message.get("text"),
57 "time": message.get("time"),
58 "sequence_id": message.get("sequence_id"),
59 "sender_id": message.get("sender_id"),
60 "recipient_id": message.get("recipient_id"),
61 "attachment": message.get("attachment"),
62 "attachments": message.get("attachments"),
63 "fetched_at": datetime.now(timezone.utc).isoformat(),
64 "raw": message,
65 }
66
67def export_history(recipient: str, *, max_pages: int = 10) -> list[dict[str, Any]]:
68 before = None
69 rows: list[dict[str, Any]] = []
70
71 for _ in range(max_pages):
72 page = fetch_dm_page(recipient, before=before)
73 conversation_id = page["conversation_id"]
74 messages = page.get("messages") or []
75
76 rows.extend(normalize_message(conversation_id, msg) for msg in messages)
77
78 if not page.get("has_more") or not messages:
79 break
80
81 oldest = min(
82 messages,
83 key=lambda msg: int(msg.get("sequence_id") or 0),
84 )
85 before = oldest.get("sequence_id")
86 if not before:
87 break
88
89 return rows
90
91if __name__ == "__main__":
92 exported = export_history("@example_user", max_pages=5)
93 print(f"Exported {len(exported)} messages")production では、次 page に進む前に現在 page を永続化します。job が止まったら、最後に保存した before から再開します。
When To Use all=true
all=true は小さい conversation や、明示的に full export が必要な internal tool には便利です。support system、CRM sync、long-running archive では、page ごとに recovery しやすい manual pagination が向いています。
conversation が十分小さく、page-level recovery が不要な場合だけ all=true を使います。
Fields To Store
| Field | Why it matters |
|---|---|
conversation_id | API が返す stable conversation key。 |
message_id | dedupe と sent message への link。 |
sequence_id | pagination checkpoint と ordering signal。 |
time | conversation timeline。 |
sender_id and recipient_id | direction と participant mapping。 |
text | search や ticket context。 |
attachment and attachments | media evidence と XChat v3 attachment metadata。 |
fetched_at | snapshot time の説明。 |
| Raw message JSON | schema change 時の reprocess 用。 |
message records に account cookie を保存しないでください。credential reference を保存します。
Common Mistakes
- large history に
all=trueを使い、page-level recovery を失う。 - current page を保存する前に
beforeを保存する。 - pagination に最新 message の
sequence_idを使う。必要なのは最古 message のsequence_idです。 @handleを permanent identity として扱う。返却されたconversation_idを保存します。- 最初の test conversation が text-only だったため attachments を捨てる。
Wrap-up
POST /v3/twitter/dm-history は user ID、@handle、conversation ID で XChat history を取得できます。信頼できる pattern は manual pagination です。page を取得し、normalized messages と raw JSON を保存し、最古 sequence_id を次の before にして、has_more が true の間続けます。