Documentation Index
Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
Use this file to discover all available pages before exploring further.
Use this workflow when a support, sales, community, or agent system needs to read direct message history and send direct messages from a connected X account. Xquik reads a participant-scoped DM conversation with GET /x/dm/{userId}/history, sends text DMs with POST /x/dm/{userId}, and can attach one uploaded media item with media_ids.
When to use this workflow
| Need | Use |
|---|
| Convert a username to a recipient ID | GET /x/users/{id} |
| Read direct message history | GET /x/dm/{userId}/history with account |
| Send a text direct message | POST /x/dm/{userId} with account and text |
| Send a direct message with media | Upload media first, then pass one mediaId in media_ids |
| Avoid bad retries | Retry only 429 and 503; fix 400, 402, 403, and 422 first |
Data you get
| Endpoint | Returned data |
|---|
GET /x/users/{id} | Recipient id, username, name, optional canDm, and profile fields |
GET /x/dm/{userId}/history | messages, has_next_page, and next_cursor |
POST /x/dm/{userId} | messageId and success |
POST /x/media | mediaId, mediaUrl, and success |
Step 1: Look up the recipient user ID
POST /x/dm/{userId} requires the numeric X user ID in the path. If you only have a username, call GET /x/users/{id} first.
curl https://xquik.com/api/v1/x/users/xquikcom \
-H "x-api-key: xq_YOUR_KEY_HERE" | jq
{
"id": "987654321",
"username": "xquikcom",
"name": "Xquik",
"canDm": true
}
When canDm is returned, use it as a preflight hint. If the field is omitted, rely on the DM write response.
Step 2: Read direct message history
Use the sender account as the account query parameter. The connected account must be a participant in the conversation, because direct messages are private and user-scoped.
curl -G https://xquik.com/api/v1/x/dm/987654321/history \
--data-urlencode "account=myxhandle" \
-H "x-api-key: xq_YOUR_KEY_HERE" | jq
{
"messages": [
{
"id": "1893726451029384191",
"text": "Can you send the setup link?",
"senderId": "987654321",
"receiverId": "123456789",
"createdAt": "2026-02-24T10:00:00.000Z"
}
],
"has_next_page": true,
"next_cursor": "1893726451029384190"
}
For older messages, pass the previous next_cursor as cursor. Store each message id, text, senderId, receiverId, createdAt, and optional mediaUrl in your support ticket, CRM note, or JSON export.
Sync and retry rules
| Rule | Why it matters |
|---|
Treat next_cursor as opaque | Pass it back as cursor for the next page. Do not decode it or build your own cursor. |
Store every message id | Use the ID to dedupe support tickets, CRM notes, warehouse rows, or JSON exports. |
| Use a participant account | GET /x/dm/{userId}/history returns 400 account_required without account and 403 dm_not_permitted when the connected account is not in the conversation. |
| Avoid legacy pagination in new code | Use cursor; keep maxId only for older integrations that already depend on it. |
| Retry only transient failures | Retry 429 and 503; do not retry 403 dm_not_permitted with the same non-participant account. |
Step 3: Send a text direct message
Use a connected account as the sender. The account value can be the connected account username or account ID.
curl -X POST https://xquik.com/api/v1/x/dm/987654321 \
-H "x-api-key: xq_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"account": "myxhandle",
"text": "Thanks for reaching out. Here is the next step."
}' | jq
{
"messageId": "1893726451029384192",
"success": true
}
Upload media first, then send exactly one uploaded media ID in media_ids.
curl -X POST https://xquik.com/api/v1/x/media \
-H "x-api-key: xq_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"account": "myxhandle",
"url": "https://example.com/support-image.png"
}' | jq
curl -X POST https://xquik.com/api/v1/x/dm/987654321 \
-H "x-api-key: xq_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"account": "myxhandle",
"text": "Here is the requested image.",
"media_ids": ["1893726451023847424"]
}' | jq
DMs accept one uploaded media ID. Do not pass multiple IDs, an empty array, or reply_to_message_id.
Costs
| Action | Cost |
|---|
Look up recipient with GET /x/users/{id} | 1 credit per call |
Read DM history with GET /x/dm/{userId}/history | 1 credit per message returned |
Upload media with POST /x/media | 10 credits per upload call |
Send DM with POST /x/dm/{userId} | 10 credits per call |
Error handling
| Failure | What to do |
|---|
400 invalid_input | Check account, text, userId, and one-item media_ids. |
400 account_required | Pass the connected sender handle as account when reading DM history. |
402 no_subscription or 402 insufficient_credits | Subscribe or top up credits before retrying. |
403 account_needs_reauth | Reconnect the sender account from the dashboard. |
403 dm_not_permitted | Use a connected account that participates in the conversation, or reconnect the account. |
422 x_rejected or related write codes | Check the recipient, account state, and message content before retrying. |
429 or 503 | Retry with exponential backoff and respect Retry-After when present. |
Handoff checklist
| Handoff | Required detail |
|---|
| Sender | Connected X account username or ID in account. |
| Recipient | Numeric user ID in POST /x/dm/{userId}. |
| History | For DM history exports, store messages, has_next_page, and next_cursor; pass cursor to fetch older messages. |
| Text | Required text, up to 10,000 characters. |
| Media | Optional one-item media_ids array containing a mediaId from POST /x/media. |
| Response | Store messageId for audit trails or support logs. |