How to Get XChat Group DM History with TwexAPI
Group DM history needs a little more structure than a one-to-one export. The same message stream may involve a customer, a support owner, a campaign manager, and several reviewers, so the export should preserve the group ID, sender IDs, timestamps, attachments, and the pagination cursor that shows where the archive stopped.
TwexAPI's Get DM History (XChat v3) endpoint reads XChat history with POST /v3/twitter/dm-history. For a group conversation, pass an existing group ID beginning with g... as recipient.
This endpoint reads an existing conversation. It does not create a group, add members, or send a message.
Endpoint and Group Request Fields
Use POST /v3/twitter/dm-history with a known XChat group ID.
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | Existing group ID beginning with 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 group 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 Group History 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": "g1234567890",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234",
"count": 50,
"all": false
}'The response includes conversation_id, has_more, and messages.
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "conversation_id": "g1234567890",
6 "has_more": true,
7 "messages": [
8 {
9 "id": "1234567890123456789",
10 "text": "The launch report is ready for review.",
11 "time": "2024-01-01T12:00:00Z",
12 "sequence_id": "2076661179334979585",
13 "sender_id": "1928096185664843776",
14 "recipient_id": "g1234567890",
15 "attachment": null,
16 "attachments": null
17 }
18 ]
19 }
20}When has_more is true, take the oldest message's sequence_id from the current page and send it as before in the next request.
Python Group History Exporter
Use a group registry instead of accepting raw g... values from unreviewed user input. That keeps the export tied to a known business purpose.
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
13GROUPS = {
14 "launch_ops": "g1234567890",
15 "creator_review": "g9876543210",
16}
17
18def resolve_group_id(group_key: str) -> str:
19 group_id = GROUPS.get(group_key)
20 if not group_id:
21 raise ValueError(f"Unknown group key: {group_key}")
22 if not group_id.startswith("g"):
23 raise ValueError("XChat group recipient must start with 'g'")
24 return group_id
25
26def fetch_group_history_page(
27 group_key: str,
28 *,
29 before: str | None = None,
30 count: int = 50,
31) -> dict[str, Any]:
32 if count < 10 or count > 200:
33 raise ValueError("count must be between 10 and 200")
34
35 payload: dict[str, Any] = {
36 "recipient": resolve_group_id(group_key),
37 "cookie": X_COOKIE,
38 "pin": XCHAT_PIN,
39 "count": count,
40 "all": False,
41 }
42 if before:
43 payload["before"] = before
44 if PROXY:
45 payload["proxy"] = PROXY
46
47 response = requests.post(
48 API_URL,
49 headers={
50 "Authorization": f"Bearer {BEARER_TOKEN}",
51 "Content-Type": "application/json",
52 "Accept": "application/json",
53 },
54 json=payload,
55 timeout=60,
56 )
57 response.raise_for_status()
58 data = response.json()
59
60 if data.get("code") != 200:
61 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
62
63 return data["data"]
64
65def normalize_group_message(
66 group_key: str,
67 group_id: str,
68 conversation_id: str,
69 message: dict[str, Any],
70) -> dict[str, Any]:
71 return {
72 "group_key": group_key,
73 "group_id": group_id,
74 "conversation_id": conversation_id,
75 "message_id": message["id"],
76 "text": message.get("text"),
77 "time": message.get("time"),
78 "sequence_id": message.get("sequence_id"),
79 "sender_id": message.get("sender_id"),
80 "recipient_id": message.get("recipient_id"),
81 "attachment": message.get("attachment"),
82 "attachments": message.get("attachments"),
83 "fetched_at": datetime.now(timezone.utc).isoformat(),
84 "raw": message,
85 }
86
87def export_group_history(
88 group_key: str,
89 *,
90 max_pages: int = 10,
91) -> list[dict[str, Any]]:
92 before = None
93 rows: list[dict[str, Any]] = []
94 group_id = resolve_group_id(group_key)
95
96 for _ in range(max_pages):
97 page = fetch_group_history_page(group_key, before=before)
98 conversation_id = page["conversation_id"]
99 messages = page.get("messages") or []
100
101 rows.extend(
102 normalize_group_message(group_key, group_id, conversation_id, msg)
103 for msg in messages
104 )
105
106 if not page.get("has_more") or not messages:
107 break
108
109 oldest = min(
110 messages,
111 key=lambda msg: int(msg.get("sequence_id") or 0),
112 )
113 before = oldest.get("sequence_id")
114 if not before:
115 break
116
117 return rows
118
119if __name__ == "__main__":
120 exported = export_group_history("launch_ops", max_pages=5)
121 print(f"Exported {len(exported)} group messages")For production, write each page before you request the next one. If the export fails after page three, restart from the last saved before value rather than calling the whole archive again.
Group History Workflow
- Register the group: Store each allowed
g...ID with an internal key, owner, purpose, and allowed reader account. - Request a page: Call
POST /v3/twitter/dm-historywithall: falseand a page size between 10 and 200. - Persist the page: Save normalized rows and the raw message JSON before updating the checkpoint.
- Advance the cursor: Use the oldest message's
sequence_idas the nextbefore. - Stop deliberately: End when
has_moreis false, there are no messages, or your job reaches its configured page limit.
The page limit is intentional. Group histories can be long, and a bounded job is easier to monitor and retry.
When To Use all=true
all=true can be useful for a small group conversation where you explicitly want a complete export in one call. It is less useful for operations logs, customer evidence, or compliance-style archives because you lose page-level recovery.
For recurring exports, keep all: false, store the cursor, and advance through the history one page at a time.
Fields To Store
| Field | Why it matters |
|---|---|
group_key | Human-readable internal group label. |
group_id | The XChat g... recipient value used in the request. |
conversation_id | Conversation key returned by the API. |
message_id | Deduplication and lookup. |
sequence_id | Pagination checkpoint and ordering signal. |
sender_id | Shows which participant wrote the message. |
recipient_id | Confirms the message belongs to the expected group. |
time | Conversation timeline. |
text | Search, ticket notes, and review context. |
attachment and attachments | Media evidence and XChat v3 attachment metadata. |
fetched_at | Explains when the snapshot was captured. |
| Raw message JSON | Lets you reprocess data if your schema changes. |
Do not store the full account cookie with the archive. Store a credential reference and keep the secret in your secret manager.
Common Mistakes
- Passing a user ID or
@handlewhen the workflow is supposed to read a group. - Letting users type raw
g...IDs into an export form without review. - Using the newest message's
sequence_idasbefore. - Calling
all=truefor a long group history and losing checkpoint recovery. - Dropping attachment fields because the first page only contained text messages.
Wrap-up
Use POST /v3/twitter/dm-history with a g... recipient to read an existing XChat group history. The reliable pattern is a group registry, manual pagination with the oldest sequence_id, raw JSON storage, and a checkpoint that lets the export resume cleanly.