List conversations
Your DM inbox: conversations with participants and settings, and with the passcode their latest messages.
/client/dm/inboxYour account, your proxy, your risk.
Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules. Automated posting, following or liking can get the account limited or suspended; that is between you and X. Treat the cookie like a password: it gives full access to the account. Trial keys cannot call these endpoints.
Without a passcode you get the conversations, their participants and settings, but no message text. With the passcode each conversation also carries its latest messages, decrypted.
Parameters
cookiebody · string · requiredCookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present.
proxybody · string · requiredYour proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request.
passcodebody · string · optionalYour X Chat passcode, the one you set up for encrypted messages on X. Without it the conversations are listed without messages. Send it as a string (a number works too, but loses leading zeros), up to 64 characters. It unlocks your chat keys for this request and is not saved. The unlocked keys stay in the server's memory for up to 30 minutes, so follow-up calls need no new unlock.
countbody · integer · optional · default 20Conversations to return, 1 to 50.
Body parameters are sent as a JSON object with Content-Type: application/json.
Notes
- 501 and the passcode errors only occur when a passcode is sent.
Example request
curl -X POST 'https://api.twittrapi.com/client/dm/inbox' \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","passcode":"1234","count":20}'import httpx
resp = httpx.post(
"https://api.twittrapi.com/client/dm/inbox",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={
"cookie": "auth_token=...; ct0=...; twid=u%3D...",
"proxy": "http://user:pass@host:port",
"passcode": "1234",
"count": 20,
},
timeout=60,
)
print(resp.status_code, resp.json())const resp = await fetch("https://api.twittrapi.com/client/dm/inbox", {
method: "POST",
headers: { "X-API-KEY": process.env.TWITTRAPI_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
cookie: "auth_token=...; ct0=...; twid=u%3D...",
proxy: "http://user:pass@host:port",
passcode: "1234",
count: 20,
}),
});
console.log(resp.status, await resp.json());Base URL https://api.twittrapi.com, key in the X-API-KEY header. The SDKs do not wrap account actions, so the examples are plain HTTP calls.
Response
{conversations: [...], message_requests_count, decrypted}. decrypted is true when a passcode was sent. Each conversation: {conversation_id, type (direct or group), name (groups), muted, disappearing_messages {enabled, ttl_seconds}, screen_capture_blocking, participants [{id, screen_name, name, avatar, is_blue_verified}], detail (X's conversation_detail)}, messages (with passcode: the latest ones, newest first), undecrypted (events that could not be read: {id, sender_id, error}), decrypt_error (when the conversation could not be decrypted) and events (other decrypted events such as read receipts and key changes). Each message: id (sequence ID, also the cursor unit), message_id, conversation_id, sender_id, created_at and expires_at (Unix milliseconds; expires_at only in chats with disappearing messages), type (text, reaction, reaction_removed, edit, deleted, ...), text, verified (the sender's signature was checked) and content (the decrypted message as X's chat library returns it: reactions, replies, attachments). Expired disappearing messages are left out.
Simple format (?format=simple or X-Response-Format: simple): Collection of conversations without cursors: {data: [id, type (direct or group), name, muted, disappearingMessages {enabled, ttlSeconds}, screenCaptureBlocking, participants [{id, username, name, avatarUrl, verified}], lastMessage, messages]}. Message objects {id, senderId, createdAt, expiresAt, type, text, emoji, targetId, replyTo {id, senderId, text}, attachments [{kind, type, filename, filesizeBytes, durationMs, width, height, url, mediaHashKey}], verified}; times are RFC 3339, unknown values null. Without a passcode lastMessage is null and messages is []. Schema.
Simple format (format=simple)
{
"data": [
{
"id": "1850000000000000002:1850000000000000002",
"type": "direct",
"name": null,
"muted": false,
"disappearingMessages": {
"enabled": false,
"ttlSeconds": null
},
"screenCaptureBlocking": false,
"participants": [
{
"id": "1850000000000000002",
"username": "example_user",
"name": "Example User",
"avatarUrl": "https://pbs.twimg.com/profile_images/1850000000000000101/example.jpg",
"verified": false
}
],
"lastMessage": {
"id": "1850000000000006000",
"senderId": "1850000000000000002",
"createdAt": "2026-10-13T07:04:12Z",
"expiresAt": null,
"type": "text",
"text": "Hi! Are you free tomorrow?",
"emoji": null,
"targetId": null,
"replyTo": null,
"attachments": [],
"verified": true
},
"messages": [
{
"id": "1850000000000006000",
"senderId": "1850000000000000002",
"createdAt": "2026-10-13T07:04:12Z",
"expiresAt": null,
"type": "text",
"text": "Hi! Are you free tomorrow?",
"emoji": null,
"targetId": null,
"replyTo": null,
"attachments": [],
"verified": true
}
]
},
{
"id": "1850000000000000001:1850000000000000002",
"type": "direct",
"name": null,
"muted": false,
"disappearingMessages": {
"enabled": false,
"ttlSeconds": null
},
"screenCaptureBlocking": true,
"participants": [
{
"id": "1850000000000000001",
"username": "other_user",
"name": "Other User",
"avatarUrl": "https://pbs.twimg.com/profile_images/1850000000000000102/other.jpg",
"verified": false
}
],
"lastMessage": null,
"messages": []
}
],
"nextCursor": null,
"previousCursor": null,
"hasNextPage": false
}In the raw format, field names and nesting come from X and can change without notice. See Response format; the simple format keeps a fixed schema.
Errors
- 400The body is not valid JSON.
- 422cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. count is not a number from 1 to 50. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again").
- 407The proxy is unreachable or timed out.
- 422Wrong passcode: "Wrong passcode (N attempts left before X deletes the chat keys)". X deletes the chat keys after too many wrong passcodes, so the API never sends the same wrong passcode to X twice (for 24 hours it answers 422 at once), and once only 3 attempts are left it does not try at all: "Only a few passcode attempts are left for this account; unlock Chat in the X app with the correct passcode first".
- 422The account has no chat passcode yet: open Chat on x.com once and set one up.
- 501Encrypted messages are not available on this server (the passcode cannot be used). Refunded.
- 502The chat keys could not be unlocked ("Could not unlock the chat keys, try again") or the messages could not be decrypted. Refunded.
- 429X's rate limit for this account: "X rate limit reached for this account, try again later".
- X'sX refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422.
Errors every endpoint can return (401, 402, 403, 404, 408, 409, 429, 5xx), and which of them are charged, are on the Errors page. Calls that fail with 400, 408, 409 or a 5xx error are refunded.