How to Send XChat Group DMs with TwexAPI
Some DM workflows are not one-to-one. A support team may need to follow up in an existing customer group, a creator program may coordinate with a small campaign group, or an operations team may send one approved update to a group conversation that already exists on X.
TwexAPI's Send DM (XChat v3) endpoint supports this by accepting an existing group ID in recipient. The group ID must begin with g...; the endpoint sends to the group, but it does not create a new group or add members.
Treat group DMs as higher-risk than one-to-one messages. One mistake reaches multiple people, so the safest implementation is: verify the group, approve the copy, send once, and store the returned message object.
Endpoint and Group Recipient
Use POST /v3/twitter/send-dm with a recipient value that is an existing XChat group ID.
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | Existing group ID beginning with g.... |
text | string | Yes | Group message body. Keep it specific to that group. |
cookie | string | Yes | X authentication cookie or auth_token for the sending account. |
media_urls | string array or null | No | Public URL for one image. Maximum one item. Cannot be used with video_url. |
video_url | string or null | No | Public URL for one video. Cannot be used with media_urls. |
pin | string or null | No | Identity PIN. The API default is 1234. |
proxy | string or null | No | Optional HTTP proxy for the sending session. |
The documentation lists this endpoint at $0.0025 per call. The important operational detail is not cost; it is making sure a group message is intentional and auditable.
Basic Group Message Request
curl --request POST \
--url https://api.twexapi.io/v3/twitter/send-dm \
--header "Authorization: Bearer $TWEXAPI_BEARER_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"recipient": "g1234567890",
"text": "Update for the launch group: the export fix is live. Please rerun the latest report before sharing screenshots.",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234"
}'The response returns a message object in data.
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "id": "1234567890123456789",
6 "text": "Update for the launch group: the export fix is live.",
7 "time": "2024-01-01T12:00:00Z",
8 "attachment": null,
9 "attachments": null,
10 "sequence_id": "2076661179334979585",
11 "sender_id": "1234567890123456789",
12 "recipient_id": "g1234567890"
13 }
14}Store the returned id, sequence_id, sender_id, recipient_id, time, and raw response. For group workflows, also store the internal group label so reviewers know what the g... ID represents.
Python Group Sender
This script requires a group mapping and an approval state before it sends. It does not let callers pass an arbitrary group ID from a form and immediately send a message.
1import os
2from datetime import datetime, timezone
3from typing import Any
4
5import requests
6
7API_URL = "https://api.twexapi.io/v3/twitter/send-dm"
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 validate_group_message(text: str) -> str:
27 cleaned = text.strip()
28 if not cleaned:
29 raise ValueError("text is required")
30 return cleaned
31
32def send_group_dm(
33 group_key: str,
34 text: str,
35 *,
36 media_urls: list[str] | None = None,
37 video_url: str | None = None,
38) -> dict[str, Any]:
39 if media_urls and video_url:
40 raise ValueError("media_urls and video_url are mutually exclusive")
41 if media_urls and len(media_urls) > 1:
42 raise ValueError("media_urls supports at most one image")
43
44 payload: dict[str, Any] = {
45 "recipient": resolve_group_id(group_key),
46 "text": validate_group_message(text),
47 "cookie": X_COOKIE,
48 "pin": XCHAT_PIN,
49 }
50
51 if media_urls:
52 payload["media_urls"] = media_urls
53 if video_url:
54 payload["video_url"] = video_url
55 if PROXY:
56 payload["proxy"] = PROXY
57
58 response = requests.post(
59 API_URL,
60 headers={
61 "Authorization": f"Bearer {BEARER_TOKEN}",
62 "Content-Type": "application/json",
63 "Accept": "application/json",
64 },
65 json=payload,
66 timeout=60,
67 )
68 response.raise_for_status()
69 data = response.json()
70
71 if data.get("code") != 200:
72 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
73
74 return data
75
76def normalize_group_send(
77 draft: dict[str, Any],
78 response: dict[str, Any],
79) -> dict[str, Any]:
80 message = response.get("data") or {}
81 return {
82 "draft_id": draft["draft_id"],
83 "group_key": draft["group_key"],
84 "group_id": resolve_group_id(draft["group_key"]),
85 "message_id": message.get("id"),
86 "sequence_id": message.get("sequence_id"),
87 "sender_id": message.get("sender_id"),
88 "recipient_id": message.get("recipient_id"),
89 "sent_at": message.get("time") or datetime.now(timezone.utc).isoformat(),
90 "api_code": response.get("code"),
91 "api_msg": response.get("msg"),
92 "raw_response": response,
93 }
94
95if __name__ == "__main__":
96 draft = {
97 "draft_id": "launch-group-update-2026-07-16",
98 "group_key": "launch_ops",
99 "text": "Update for the launch group: the export fix is live. Please rerun the latest report before sharing screenshots.",
100 "status": "approved",
101 }
102
103 if draft["status"] != "approved":
104 raise ValueError("Group DM draft must be approved before sending")
105
106 result = send_group_dm(draft["group_key"], draft["text"])
107 print(normalize_group_send(draft, result))Run it from a trusted server environment:
TWEXAPI_BEARER_TOKEN="your_twexapi_bearer_token" \
TWEXAPI_X_COOKIE="auth_token=...; ct0=...; twid=..." \
TWEXAPI_XCHAT_PIN="1234" \
python send_xchat_group_dm.pyThe GROUPS map is intentionally explicit. In production, load that mapping from your database and only allow approved internal group records.
Attach One Image or One Video
The same endpoint supports media for group DMs, but the media rules are unchanged: one image through media_urls, or one video through video_url, not both.
image_result = send_group_dm(
"launch_ops",
"Here is the final launch checklist screenshot.",
media_urls=["https://example.com/checklist.jpg"],
)video_result = send_group_dm(
"creator_review",
"Short walkthrough for the revised brief.",
video_url="https://example.com/brief-walkthrough.mp4",
)Use stable public URLs and avoid logging full signed URLs. If the media is sensitive, do not route it through public links unless your security model explicitly allows it.
Group DM Workflow
A group message should have a stronger review path than a one-to-one message.
- Group registry: Store each approved group ID with an internal label, owner, purpose, and allowed sender accounts.
- Draft: Create the message against a group key, not a raw
g...value from user input. - Review: Confirm the group purpose, message text, media choice, and sender account.
- Send once: Call
POST /v3/twitter/send-dmfor the approved draft. - Record: Save the returned message object, group key, group ID, sender account, and raw response.
- Follow up: Connect the message to the project, ticket, campaign, or customer record that justified the send.
This prevents the most common group-DM failure: sending a correct message to the wrong group.
Store Group Send State
| Field | Why it matters |
|---|---|
draft_id | Connects the sent message to the approved draft. |
group_key | Human-readable internal group label. |
group_id | The XChat g... recipient value. |
sender_account_id | Identifies which X account sent the message. |
message_text | Stores the approved text. |
attachment_kind | Tracks no media, image, or video. |
message_id | Returned XChat message ID. |
sequence_id | Useful for later conversation pagination. |
sent_at | Supports audit and timeline review. |
status | Tracks approved, sent, failed, or needs_review. |
raw_response | Preserves the original API evidence. |
Do not store the full account cookie in this table. Store a credential reference instead.
Common Mistakes
- Treating
POST /v3/twitter/send-dmas a group creation endpoint. It sends to an existing group only. - Accepting raw
g...IDs from an unreviewed form and sending immediately. - Sending a generic broadcast message that does not match the group's purpose.
- Forgetting that
media_urlssupports only one image. - Sending both
media_urlsandvideo_url. - Retrying after an unknown timeout without checking whether the group already received the message.
For unknown failures, check the group conversation or your message history before retrying. A second send may duplicate the update.
Wrap-up
To send a DM to an XChat group with TwexAPI, call POST /v3/twitter/send-dm and set recipient to an existing g... group ID. The strongest production setup uses a group registry, message approval, one send attempt per approved draft, and durable storage for the returned message object.
That keeps group messaging useful without turning it into uncontrolled broadcasting.