GET /x/dm/{userId}/history, sends text DMs with POST /x/dm/{userId}, and can attach one uploaded media item with media_ids.
Choose the DM path
Text-only send
Call
POST /x/dm/{userId} with account and non-empty text. Store
messageId, success, sender account, and recipient ID.Media send
Call
POST /x/media first, then send media_ids: ["<mediaId>"] on
POST /x/dm/{userId}. Use exactly 1 item and store media_id beside
messageId.History sync
Call
GET /x/dm/{userId}/history with account before replying when the
workflow needs private conversation context. Store messages,
has_next_page, and next_cursor.Username input
Call
GET /x/users/{id} first when the app only has a username. DM sends
require the numeric recipient ID in the path.When to use this workflow
Look up the recipient
Use
GET /x/users/{id} to convert a username to the numeric recipient ID required by DM writes.Read message history
Use
GET /x/dm/{userId}/history with account to sync participant-scoped messages.Send a text DM
Use
POST /x/dm/{userId} with account and text, then store the returned messageId.Attach one media item
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
Recipient profile
GET /x/users/{id} returns recipient id, username, name, optional canDm, and profile fields.History page
GET /x/dm/{userId}/history returns messages, has_next_page, and next_cursor.Send result
POST /x/dm/{userId} returns messageId and success for outbound handoff storage.Media upload
POST /x/media returns mediaId, mediaUrl, and success before the one-item DM attachment send.End-to-end direct message handoff
Use one checkpoint object after recipient lookup, history sync, a text send, and an optional media send. Keep full DM bodies in restricted support, CRM, warehouse, or agent memory systems; shared run logs should carry IDs, status, cursors, and media references.Recipient checkpoint
Store the numeric
id, username, and optional canDm preflight hint from user lookup.History checkpoint
Store
messages_count, next_cursor, and has_next_page for each synced history page.Text send checkpoint
Store the
message_id, success, send_status, sender account, and recipient ID after POST /x/dm/{userId}.Media checkpoint
Store exactly one uploaded
media_id beside the returned DM message_id.Audit checkpoint
Keep shared audit rows limited to IDs, cursors, status, endpoints, and media references.
Invalid fields
Do not pass
reply_to_message_id, empty media_ids, or more than one media ID.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.
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 theaccount query parameter. The connected account must be a participant in the conversation, because direct messages are private and user-scoped.
next_cursor as cursor. Store each message id, text, senderId, receiverId, createdAt, and optional mediaUrl in your support ticket, CRM note, or JSON export.
DM history and outbound
message_text values can contain private customer or community conversations. Store them only in private support, CRM, warehouse, or agent memory systems. Shared logs, public artifacts, and status dashboards should keep message_id, sender_id, receiver_id, created_at, media_url, and job status instead of full DM bodies.Sync and retry rules
Opaque cursor
Treat
next_cursor as opaque. Pass it back as cursor for the next page.
Do not decode it or build your own cursor.Store message IDs
Store every message
id. Use the ID to dedupe support tickets, CRM notes,
warehouse rows, or JSON exports.Participant account
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.Modern pagination
Use
cursor; keep maxId only for older integrations that already depend on it.Transient retries only
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. Theaccount value can be the connected account username or account ID.
Store the outbound handoff
After a200 response, persist one outbound record before handing control back to a CRM, ticket, queue, or agent. POST /x/dm/{userId} returns messageId and success; use your own job timestamp if the downstream system needs sent_at.
media_ids in the request and
media_id in the handoff only for the media send in Step 4.
When you also sync history, normalize each messages[] item separately with message_id, sender_id, receiver_id, created_at, optional media_url, and conversation_user_id.
JSON Lines handoff
For queue, warehouse, CRM, or agent memory, write one record per history message or outbound send toxquik-dm-handoff.jsonl.
media_url only when the message has media. Text-only send
rows omit media_id and media_ids; media send rows use the Step 4 shape.
Step 4: Send a direct message with media
Upload media first, then send exactly one uploaded media ID inmedia_ids.
reply_to_message_id.
The media DM send returns the same messageId and success fields as a text
DM. Store the uploaded media_id beside that returned message ID so attachment
audits can join the upload, recipient, sender account, and outbound message.
Costs
Recipient lookup
GET /x/users/{id} costs 1 credit per call.DM history
GET /x/dm/{userId}/history costs 1 credit per message returned.Media upload
POST /x/media costs 10 credits per upload call before a media DM send.DM send
POST /x/dm/{userId} costs 10 credits per call and returns messageId.Error handling
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 billing state
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_dm_not_allowed
422 x_dm_not_allowed. The recipient may not accept DMs from this connected account. Do not retry unchanged; use another permitted account or ask the recipient to allow messages.422 x_rejected
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
Sender
Store the connected X account username or ID sent in
account.Recipient
Store the numeric recipient ID used in the
POST /x/dm/{userId} path.History
For DM history exports, store
messages, has_next_page, and
next_cursor; pass cursor to fetch older messages.Text
Store the required non-empty
text value.Media
Store the optional one-item
media_ids array containing a mediaId from
POST /x/media.Response
Store
messageId, recipient_user_id, sender_account, message_text,
and optional media_id in private audit records or support systems.JSON Lines
Store history and send records in
xquik-dm-handoff.jsonl for queues, warehouse loads, CRM syncs, or agent memory.Related: Send Direct Message · Get DM History · Get User · Media Upload Workflow