How to Send XChat DMs with TwexAPI
Sending a DM is a write action, so the most important design choice is not the request library. It is the workflow around the send: who approved the message, which account is sending it, whether the recipient is reachable, and where the final message ID is stored.
TwexAPI's Send DM (XChat v3) endpoint sends a message through XChat's encrypted chat API. It supports one-to-one recipients by user ID or @handle, and existing group conversations by a g... group ID. The endpoint can also attach one image through media_urls or one video through video_url.
Use this endpoint for approved support follow-ups, creator operations, partnership conversations, or CRM tasks where you already have a legitimate reason to contact the recipient. Do not build blind bulk-send loops around it.
Endpoint and Request Fields
Use POST /v3/twitter/send-dm when your application has an approved message ready to send.
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | X user ID, @handle, or existing group ID beginning with g.... |
text | string | Yes | Message body. Validate it before sending. |
cookie | string | Yes | X authentication cookie or auth_token. Keep it server-side. |
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 http(s) URL for one video. Cannot be used with media_urls. |
pin | string or null | No | Identity PIN. The API default is 1234; use your own secured value when configured. |
proxy | string or null | No | Optional HTTP proxy for the sending session. |
The docs list this endpoint at $0.0025 per call. Treat that as the API call price, not as permission to send without review.
Basic 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": "@example_user",
"text": "Hi, thanks for asking about the export issue. We shipped a fix and can help verify your latest run.",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234"
}'A successful response returns a message object in data.
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "id": "1234567890123456789",
6 "text": "Hi, thanks for asking about the export issue.",
7 "time": "2024-01-01T12:00:00Z",
8 "attachment": null,
9 "attachments": null,
10 "sequence_id": "2076661179334979585",
11 "sender_id": "1234567890123456789",
12 "recipient_id": "9876543210987654321"
13 }
14}Save at least data.id, sequence_id, sender_id, recipient_id, time, and the raw response. Those fields help you reconcile CRM actions, support tickets, and later conversation history.
Python Client with Send Approval
This script reads credentials from environment variables, validates the request, and refuses to send unless the draft is explicitly approved.
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
13def validate_dm_payload(
14 recipient: str,
15 text: str,
16 *,
17 media_urls: list[str] | None = None,
18 video_url: str | None = None,
19) -> None:
20 if not recipient or not recipient.strip():
21 raise ValueError("recipient is required")
22 if not text or not text.strip():
23 raise ValueError("text is required")
24 if media_urls and video_url:
25 raise ValueError("media_urls and video_url are mutually exclusive")
26 if media_urls and len(media_urls) > 1:
27 raise ValueError("media_urls supports at most one image")
28
29def send_xchat_dm(
30 recipient: str,
31 text: str,
32 *,
33 media_urls: list[str] | None = None,
34 video_url: str | None = None,
35) -> dict[str, Any]:
36 validate_dm_payload(
37 recipient,
38 text,
39 media_urls=media_urls,
40 video_url=video_url,
41 )
42
43 payload: dict[str, Any] = {
44 "recipient": recipient.strip(),
45 "text": text.strip(),
46 "cookie": X_COOKIE,
47 "pin": XCHAT_PIN,
48 }
49
50 if media_urls:
51 payload["media_urls"] = media_urls
52 if video_url:
53 payload["video_url"] = video_url
54 if PROXY:
55 payload["proxy"] = PROXY
56
57 response = requests.post(
58 API_URL,
59 headers={
60 "Authorization": f"Bearer {BEARER_TOKEN}",
61 "Content-Type": "application/json",
62 "Accept": "application/json",
63 },
64 json=payload,
65 timeout=60,
66 )
67 response.raise_for_status()
68 data = response.json()
69
70 if data.get("code") != 200:
71 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
72
73 return data
74
75def normalize_send_result(
76 draft: dict[str, Any],
77 response: dict[str, Any],
78) -> dict[str, Any]:
79 message = response.get("data") or {}
80 return {
81 "draft_id": draft["draft_id"],
82 "recipient": draft["recipient"],
83 "message_id": message.get("id"),
84 "sequence_id": message.get("sequence_id"),
85 "sender_id": message.get("sender_id"),
86 "recipient_id": message.get("recipient_id"),
87 "sent_at": message.get("time") or datetime.now(timezone.utc).isoformat(),
88 "api_code": response.get("code"),
89 "api_msg": response.get("msg"),
90 "raw_response": response,
91 }
92
93if __name__ == "__main__":
94 draft = {
95 "draft_id": "support-followup-1042",
96 "recipient": "@example_user",
97 "text": "Hi, thanks for asking about the export issue. We shipped a fix and can help verify your latest run.",
98 "status": "approved",
99 }
100
101 if draft["status"] != "approved":
102 raise ValueError("DM draft must be approved before sending")
103
104 result = send_xchat_dm(draft["recipient"], draft["text"])
105 print(normalize_send_result(draft, result))Run it from a server or trusted workstation:
TWEXAPI_BEARER_TOKEN="your_twexapi_bearer_token" \
TWEXAPI_X_COOKIE="auth_token=...; ct0=...; twid=..." \
TWEXAPI_XCHAT_PIN="1234" \
python send_xchat_dm.pyDo not run this from a browser or client-side app. The request contains account credentials and can send real messages.
Sending Images or Videos
XChat v3 supports either one image or one video in the same DM. The fields are mutually exclusive.
image_result = send_xchat_dm(
"@example_user",
"Here is the screenshot I mentioned.",
media_urls=["https://example.com/screenshot.jpg"],
)video_result = send_xchat_dm(
"@example_user",
"Here is a short walkthrough.",
video_url="https://example.com/walkthrough.mp4",
)Keep media URLs public and stable long enough for the send operation. If your storage uses expiring signed URLs, generate them immediately before sending and log only a redacted version.
Put It After a Reachability Check
The safer workflow is: check reachability, approve the message, send, then write the result back.
- Preflight: Use
POST /v2/dm/statusfor usernames or user IDs when you need a reachability snapshot. - Draft: Store
recipient, message text, attachment choice, sender account, source workflow, and owner. - Approve: Require a human or rule-based approval state before calling the write endpoint.
- Send: Call
POST /v3/twitter/send-dmonce for the approved draft. - Record: Store
message_id,sequence_id, sender, recipient, send time, and raw response. - Follow up: Use stored IDs to connect the DM with a ticket, CRM contact, or campaign record.
This gives every message an explainable reason for being sent. It also makes failures easier to diagnose without guessing whether the recipient, content, media, cookie, or platform state caused the problem.
Store DM State
For production use, keep a message table separate from credential storage.
| Field | Why it matters |
|---|---|
dm_draft_id | Connects the sent message to the approved draft. |
recipient_input | Preserves the original @handle, user ID, or group ID. |
sender_account_id | Identifies which X account sent the message. |
message_text | Stores the approved text. |
attachment_kind | Distinguishes no media, image, or video. |
message_id | The returned XChat message ID. |
sequence_id | Useful for later conversation pagination. |
sent_at | Supports CRM, support, and audit timelines. |
status | Tracks approved, sent, failed, or needs_review. |
raw_response | Keeps the original API evidence. |
Never store the full cookie in the message row. Store a secret reference or account credential ID instead.
Common Mistakes
- Sending before checking whether the recipient is reachable.
- Treating
@handleas permanent; store returnedrecipient_idwhen available. - Sending both
media_urlsandvideo_urlin one request. - Passing more than one image in
media_urls. - Logging account cookies, auth tokens, or full signed media URLs.
- Retrying unknown failures without checking whether the message was already sent.
For unknown timeouts, check conversation state before retrying. A second send may create a duplicate message.
Wrap-up
POST /v3/twitter/send-dm is the TwexAPI XChat v3 endpoint for sending approved DMs to one-to-one recipients or existing groups. The production pattern is straightforward: verify reachability when needed, approve the draft, send once, store the returned message object, and route uncertain failures to review.
That keeps DM automation useful without turning it into uncontrolled messaging.