Skip to main content
GET
/
x
/
dm
/
{userId}
/
history
Get DM history
curl --request GET \
  --url https://xquik.com/api/v1/x/dm/{userId}/history \
  --header 'x-api-key: <api-key>'
import requests

url = "https://xquik.com/api/v1/x/dm/{userId}/history"

headers = {"x-api-key": "<api-key>"}

response = requests.get(url, headers=headers)

print(response.text)
const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};

fetch('https://xquik.com/api/v1/x/dm/{userId}/history', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
package main

import (
"fmt"
"net/http"
"io"
)

func main() {

url := "https://xquik.com/api/v1/x/dm/{userId}/history"

req, _ := http.NewRequest("GET", url, nil)

req.Header.Add("x-api-key", "<api-key>")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
1 credit per result returned · All plans from $0.00012/credit
Use this endpoint to sync participant-scoped DM pages before a support, CRM, warehouse, or agent workflow replies. Pass the connected participant account in account, store message IDs plus next_cursor, and keep full message text only in private systems.
Requires a connected X account passed via the account query parameter. DM history is participant-scoped, so pass the connected account that belongs to the conversation.
DM history responses can contain private message text. Store them in a private support, CRM, warehouse, or agent memory system. Do not write full DM bodies to shared logs or public artifacts.

Which DM workflow?

Read conversation history

Use GET /x/dm/{userId}/history with account, then store messages[].id and next_cursor in a private system.

Send text reply

Use POST /x/dm/{userId}, pass the same connected account, and store the returned messageId.

Send one media item

Use POST /x/media first, then pass the returned media ID as the only media_ids item on the DM send.

Resolve participant ID

Use GET /x/users/{id} when a workflow starts from a handle and needs the numeric userId for history or send calls.
curl -G https://xquik.com/api/v1/x/dm/44196397/history \
  --data-urlencode "account=your_handle" \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq

# Page 2
curl -G https://xquik.com/api/v1/x/dm/44196397/history \
  --data-urlencode "account=your_handle" \
  --data-urlencode "cursor=abc123" \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
const userId = "44196397";
const account = "your_handle";

function historyUrl(userId, account, cursor) {
  const params = new URLSearchParams({ account });
  if (cursor) params.set("cursor", cursor);
  return `https://xquik.com/api/v1/x/dm/${userId}/history?${params}`;
}

function toDmHistoryRows(page, { account, userId }) {
  return page.messages.map((message) => ({
    record_type: "dm_history",
    conversation_user_id: userId,
    sender_account: account,
    message_id: message.id,
    sender_id: message.senderId,
    receiver_id: message.receiverId,
    message_text: message.text ?? null,
    created_at: message.createdAt ?? null,
    media_url: message.mediaUrl ?? null,
    page_next_cursor: page.has_next_page ? page.next_cursor : null,
    source_endpoint: `/api/v1/x/dm/${userId}/history`,
  }));
}

async function savePrivateDmHistoryRows(rows) {
  // Replace this with a private CRM, warehouse, or agent memory write.
  return rows.length;
}

const response = await fetch(historyUrl(userId, account), {
  headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
});
const data = await response.json();
const historyRows = toDmHistoryRows(data, { account, userId });
await savePrivateDmHistoryRows(historyRows);

// Paginate
if (data.has_next_page) {
  const next = await fetch(historyUrl(userId, account, data.next_cursor), {
    headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
  });
  const nextData = await next.json();
  const nextRows = toDmHistoryRows(nextData, { account, userId });
  await savePrivateDmHistoryRows(nextRows);
}
import requests

def to_dm_history_rows(page, account, user_id):
    return [
        {
            "record_type": "dm_history",
            "conversation_user_id": user_id,
            "sender_account": account,
            "message_id": message["id"],
            "sender_id": message["senderId"],
            "receiver_id": message["receiverId"],
            "message_text": message.get("text"),
            "created_at": message.get("createdAt"),
            "media_url": message.get("mediaUrl"),
            "page_next_cursor": page["next_cursor"] if page["has_next_page"] else None,
            "source_endpoint": f"/api/v1/x/dm/{user_id}/history",
        }
        for message in page["messages"]
    ]

def save_private_dm_history_rows(rows):
    # Replace this with a private CRM, warehouse, or agent memory write.
    return len(rows)

response = requests.get(
    "https://xquik.com/api/v1/x/dm/44196397/history",
    params={"account": "your_handle"},
    headers={"x-api-key": "xq_YOUR_KEY_HERE"},
)
data = response.json()
history_rows = to_dm_history_rows(data, "your_handle", "44196397")
save_private_dm_history_rows(history_rows)

# Paginate
while data["has_next_page"]:
    data = requests.get(
        "https://xquik.com/api/v1/x/dm/44196397/history",
        params={"account": "your_handle", "cursor": data["next_cursor"]},
        headers={"x-api-key": "xq_YOUR_KEY_HERE"},
    ).json()
    next_rows = to_dm_history_rows(data, "your_handle", "44196397")
    save_private_dm_history_rows(next_rows)
The examples normalize each page into private dm_history rows. Store message_id, sender_id, receiver_id, message_text, created_at, optional media_url, conversation_user_id, sender_account, and page_next_cursor. Keep message_text only in private systems; use the IDs, timestamp, media URL, and job status in shared logs.

Path parameters

userId
string
required
Target X user ID for the DM conversation (numeric string).

Query parameters

account
string
required
X handle (without the @ prefix) of the connected X account used to read the conversation. DM history is participant-scoped, so the account must belong to the conversation. Connect an account on the dashboard before calling this endpoint.
cursor
string
Pagination cursor. Pass the next_cursor value from the previous response to fetch older messages.
maxId
string
Legacy pagination cursor. Use cursor for new integrations. When both are present, cursor takes precedence.

Headers

x-api-key
string
required
Your API key. Session cookie authentication is also supported.

Response

messages
object[]
Array of direct messages.
has_next_page
boolean
Whether older messages are available.
next_cursor
string
Opaque cursor for the next page. Empty string when no more messages.
{
  "messages": [
    {
      "id": "1893456789012345678",
      "text": "Hey, great tool!",
      "senderId": "44196397",
      "receiverId": "987654321",
      "createdAt": "2026-02-24T10:00:00.000Z"
    }
  ],
  "has_next_page": true,
  "next_cursor": "1893456789012345677"
}

History sync handoff

Use this endpoint when a CRM, support desk, warehouse job, or agent needs participant-scoped DM context before sending a reply.

Dedupe imported messages

Store messages[].id as the external DM ID for CRM notes, support tickets, warehouse rows, or agent memory.

Preserve participants

Store messages[].senderId and messages[].receiverId with the connected account so each private conversation stays tied to the correct sender.

Resume older pages

Store next_cursor when has_next_page is true, then pass it as cursor on the next sync job.

Keep media context

Store optional messages[].mediaUrl with messages[].createdAt when a DM includes an image, GIF, or video attachment.
Related: Direct Message Workflow for lookup, participant-scoped history sync, messageId storage, and media handoff; Get User to resolve the recipient userId; Send DM to reply from the connected account; Upload Media when a reply needs one uploaded mediaId.
Last modified on May 25, 2026