How to Get XChat DM History with TwexAPI
DM history is useful only when you can explain how it was collected. A support ticket, CRM sync, or partnership log needs the conversation ID, message IDs, timestamps, sender and recipient IDs, attachments, and the pagination cursor that proves where the export stopped.
TwexAPI's Get DM History (XChat v3) endpoint reads XChat conversation history. For one-to-one workflows, recipient can be a user ID, an @handle, or a conversation ID such as 1928096185664843776:1989842918455291904.
Endpoint and Request Fields
Use POST /v3/twitter/dm-history to read an XChat conversation.
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | User ID, @handle, conversation ID a:b, or group ID g.... |
cookie | string | Yes | X authentication cookie or auth_token. Keep it server-side. |
pin | string or null | No | Identity PIN. API default is 1234. |
count | integer or null | No | Page size from 10 to 200. Default is 50. |
all | boolean or null | No | When true, follows has_more and returns every page. Use carefully for large histories. |
before | string or null | No | For manual pagination, pass the previous page's oldest message sequence_id. |
proxy | string or null | No | Optional HTTP proxy for the session. |
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
}'The response returns conversation_id, has_more, and 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": "Thanks, I can confirm the export works now.",
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}If has_more is true, take the oldest message's sequence_id and pass it as before in the next request.
Python Manual Pagination
Manual pagination gives you safer checkpoints than all=true. Write each page before requesting the next one.
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")For production, persist each page before moving the checkpoint. If the job crashes, restart from the last saved before value instead of starting over.
When To Use all=true
all=true is convenient for small conversations or internal tools where you explicitly want a full export in one call. For support systems, CRM sync, or long-running archives, manual pagination is easier to audit because every page has a checkpoint.
Use all=true only when you know the conversation is small enough and you do not need page-level recovery.
Fields To Store
| Field | Why it matters |
|---|---|
conversation_id | Stable conversation key returned by the API. |
message_id | Deduplication and linking to a sent message. |
sequence_id | Pagination checkpoint and ordering signal. |
time | Conversation timeline. |
sender_id and recipient_id | Direction and participant mapping. |
text | Message body for search or ticket context. |
attachment and attachments | Media evidence and XChat v3 attachment metadata. |
fetched_at | Explains when the snapshot was taken. |
| Raw message JSON | Allows reprocessing if your schema changes. |
Do not store the account cookie with message records. Store a credential reference instead.
Common Mistakes
- Calling
all=truefor large histories and losing page-level recovery. - Saving
beforebefore the current page is persisted. - Using the newest message's
sequence_idinstead of the oldest one for pagination. - Treating
@handleas permanent; store the returnedconversation_id. - Dropping attachments because the first test conversation only had text.
Wrap-up
POST /v3/twitter/dm-history lets you read XChat history by user ID, @handle, or conversation ID. The reliable pattern is manual pagination: request a page, store normalized messages and raw JSON, use the oldest sequence_id as before, and repeat while has_more is true.