Documentation Index

Fetch the complete documentation index at: /llms.txt

Use this file to discover all available pages before exploring further.

Skip to main content
POST
/
x
/
dm
/
{userId}
Send DM
curl --request POST \
  --url https://xquik.com/api/v1/x/dm/{userId} \
  --header 'Content-Type: <content-type>' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'x-api-key: <api-key>' \
  --data '
{
  "account": "<string>",
  "text": "<string>",
  "media_ids": [
    "<string>"
  ]
}
'
import requests

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

payload = {
    "account": "<string>",
    "text": "<string>",
    "media_ids": ["<string>"]
}
headers = {
    "x-api-key": "<api-key>",
    "Idempotency-Key": "<idempotency-key>",
    "Content-Type": "<content-type>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
const options = {
  method: 'POST',
  headers: {
    'x-api-key': '<api-key>',
    'Idempotency-Key': '<idempotency-key>',
    'Content-Type': '<content-type>'
  },
  body: JSON.stringify({account: '<string>', text: '<string>', media_ids: ['<string>']})
};

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

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

func main() {

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

	payload := strings.NewReader("{\n  \"account\": \"<string>\",\n  \"text\": \"<string>\",\n  \"media_ids\": [\n    \"<string>\"\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-api-key", "<api-key>")
	req.Header.Add("Idempotency-Key", "<idempotency-key>")
	req.Header.Add("Content-Type", "<content-type>")

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

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

	fmt.Println(string(body))

}
{
  "object": "<string>",
  "id": "<string>",
  "writeActionId": "<string>",
  "action": "<string>",
  "status": "<string>",
  "terminal": true,
  "retryable": true,
  "safeToRetry": true,
  "statusUrl": "<string>",
  "pollAfterMs": {},
  "charged": true,
  "chargedCredits": "<string>",
  "billing": {},
  "request": {},
  "account": {},
  "target": {},
  "targetId": {},
  "result": {},
  "nextAction": {},
  "requestHash": "<string>",
  "requestId": "<string>",
  "idempotent": true,
  "error": "<string>",
  "message": "<string>",
  "sendDispatched": true,
  "sendDispatchedAt": "<string>",
  "createdAt": "<string>",
  "updatedAt": "<string>",
  "completedAt": "<string>",
  "expiresAt": "<string>",
  "confirmedAt": "<string>",
  "confirmationCheckedAt": "<string>",
  "confirmationAttempts": 123,
  "tweetId": "<string>",
  "messageId": "<string>",
  "mediaId": "<string>",
  "mediaUrl": "<string>",
  "communityId": "<string>",
  "communityName": "<string>",
  "resultId": "<string>",
  "media": {},
  "details": {},
  "success": true
}
10 credits per call · All plans from $0.00012/credit
Send a text DM from one connected X account to the recipient userId. For media DMs, upload first with POST /x/media, pass the returned mediaId as the only media_ids item, and store the returned messageId. Use public media URLs with POST /x/tweets; this DM endpoint accepts one uploaded media ID instead.
curl -X POST https://xquik.com/api/v1/x/dm/44196397 \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Idempotency-Key: dm-44196397-1895432178065391234" \
  -H "Content-Type: application/json" \
  -d '{
    "account": "myxaccount",
    "text": "Hello from Xquik!"
  }' | jq
const recipientUserId = "44196397";
const account = "myxaccount";
const uploadedMediaId = "1893726451023847424";
const mediaIds = [uploadedMediaId];

const response = await fetch("https://xquik.com/api/v1/x/dm/44196397", {
  method: "POST",
  headers: {
    "x-api-key": "xq_YOUR_KEY_HERE",
    "Idempotency-Key": "dm-44196397-1895432178065391234",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    account,
    text: "Hello from Xquik!",
    media_ids: mediaIds,
  }),
});
const result = await response.json();

const dmHandoff = {
  message_id: result.messageId,
  user_id: recipientUserId,
  account,
  send_status: result.success ? "sent" : "unknown",
  media_ids: mediaIds,
  media_id: uploadedMediaId,
  source_endpoint: `/api/v1/x/dm/${recipientUserId}`,
};

process.stdout.write(`${JSON.stringify(dmHandoff)}\n`);
import json
import requests

recipient_user_id = "44196397"
account = "myxaccount"
uploaded_media_id = "1893726451023847424"
media_ids = [uploaded_media_id]

response = requests.post(
    f"https://xquik.com/api/v1/x/dm/{recipient_user_id}",
    headers={
        "x-api-key": "xq_YOUR_KEY_HERE",
        "Idempotency-Key": "dm-44196397-1895432178065391234",
    },
    json={
        "account": account,
        "text": "Hello from Xquik!",
        "media_ids": media_ids,
    },
)
result = response.json()

dm_handoff = {
    "message_id": result["messageId"],
    "user_id": recipient_user_id,
    "account": account,
    "send_status": "sent" if result["success"] else "unknown",
    "media_ids": media_ids,
    "media_id": uploaded_media_id,
    "source_endpoint": f"/api/v1/x/dm/{recipient_user_id}",
}

print(json.dumps(dm_handoff))
package main

import (
    "bytes"
    "encoding/json"
    "log"
    "net/http"
    "os"
)

type SendDmRequest struct {
    Account  string   `json:"account"`
    Text     string   `json:"text"`
    MediaIDs []string `json:"media_ids,omitempty"`
}

type SendDmResult struct {
    MessageID string `json:"messageId"`
    Success   bool   `json:"success"`
}

type DmHandoff struct {
    MessageID      string  `json:"message_id"`
    UserID         string  `json:"user_id"`
    Account        string  `json:"account"`
    SendStatus     string  `json:"send_status"`
    MediaIDs       []string `json:"media_ids"`
    MediaID        string  `json:"media_id"`
    SourceEndpoint string  `json:"source_endpoint"`
}

func sendStatus(success bool) string {
    if success {
        return "sent"
    }
    return "unknown"
}

func main() {
    recipientUserID := "44196397"
    account := "myxaccount"
    uploadedMediaID := "1893726451023847424"
    uploadedMediaIDs := []string{uploadedMediaID}

    body, err := json.Marshal(SendDmRequest{
        Account:  account,
        Text:     "Hello from Xquik!",
        MediaIDs: uploadedMediaIDs,
    })
    if err != nil {
        log.Fatal(err)
    }

    req, err := http.NewRequest(
        "POST",
        "https://xquik.com/api/v1/x/dm/"+recipientUserID,
        bytes.NewReader(body),
    )
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE")
    req.Header.Set("Idempotency-Key", "dm-44196397-1895432178065391234")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var result SendDmResult
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        log.Fatal(err)
    }

    handoff := DmHandoff{
        MessageID:      result.MessageID,
        UserID:         recipientUserID,
        Account:        account,
        SendStatus:     sendStatus(result.Success),
        MediaIDs:       uploadedMediaIDs,
        MediaID:        uploadedMediaID,
        SourceEndpoint: "/api/v1/x/dm/" + recipientUserID,
    }

    if err := json.NewEncoder(os.Stdout).Encode(handoff); err != nil {
        log.Fatal(err)
    }
}
The Node.js, Python, and Go examples convert the response into one DM send row. Store message_id, user_id, account, send_status, optional media_id, media_ids, and source_endpoint. For media DMs, keep media_ids as the one-item request array and set media_id to the uploaded media ID that you passed as that single item.

Send with media

Upload media first with Upload Media, then pass the returned mediaId as the only media_ids item.
curl -X POST https://xquik.com/api/v1/x/dm/44196397 \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Idempotency-Key: dm-media-44196397-1895432178065391234" \
  -H "Content-Type: application/json" \
  -d '{
    "account": "myxaccount",
    "text": "Here is the requested image.",
    "media_ids": ["1893726451023847424"]
  }' | jq
media_ids must contain exactly one uploaded media ID. Empty arrays, multiple IDs, and reply_to_message_id return 400 invalid_input.
Generated SDKs can expose reply_to_message_id while the REST route rejects it. Leave it unset. Pass exactly one uploaded media ID in media_ids, then store the returned messageId with that media ID.

Media DM result handoff

After the media DM send returns 200 OK, store the returned messageId with the uploaded media ID and recipient user ID so support logs, CRM records, queues, or agent memory can reconcile the attachment with the sent message.
{
  "record_type": "dm_media_send",
  "message_id": "1893726451029384192",
  "user_id": "44196397",
  "account": "myxaccount",
  "media_ids": ["1893726451023847424"],
  "media_id": "1893726451023847424",
  "send_status": "sent",
  "handoff_format": "jsonl"
}
Keep media_ids as a one-item array in the request, keep media_id as the original POST /x/media result, and use message_id as the external DM identifier after the send succeeds.

Direct message handoff

Use POST /x/dm/{userId} when a support, sales, community, CRM, or agent workflow needs to send an auditable direct message from a connected X account. If you only have a username, look up the recipient first with GET /x/users/{id}. If the workflow needs conversation context, read the latest messages with GET /x/dm/{userId}/history before sending.

messageId

Store messageId as the external message ID for support logs, CRM records, queues, or agent memory.

success

Mark the send job complete after a 200 OK response.

userId

Keep the recipient X user ID from the path with the send job.

account

Store the connected X account that sent the DM.

text

Store the exact message text sent. Add your own sent_at timestamp when downstream systems need it.

media_ids[0]

Store the uploaded media ID when the DM includes one attachment from POST /x/media.
This endpoint costs 10 credits per send. Uploading media first with POST /x/media is a separate 10-credit call. x_dm_not_allowed means the recipient may not accept messages from this connected account; do not retry unchanged. Do not retry 422 x_dm_not_allowed unchanged. Use another permitted connected account or ask the recipient to allow messages. Open the Direct Message Workflow for queue design. Use Get DM History for approved conversation context. Use Upload Media to create the one mediaId allowed in media_ids.

Headers

string
required
Your API key. Session cookie authentication is also supported. Generate a key from the dashboard.
string
required
Unique key for this intended write. Reuse it only for an exact network replay.
string
required
Must be application/json.

Path parameters

string
required
The X user ID of the recipient.

Body

string
required
X username or account ID of your connected account to act as.
string
required
Non-empty message text to send. If X rejects oversized content, the API returns 422 x_content_too_long.
string[]
Optional one-item array containing an uploaded media ID. Upload media first with Upload Media, then pass the returned mediaId as the only array item. Empty arrays and multiple IDs are rejected.

Response

Durable lifecycle responses

Send one unique Idempotency-Key per intended write. Reuse it only for the exact same account, target, payload, and media.
Store the X-Request-Id response header with every action record.
  1. Store id, request.hash, account, target, and billing.
  2. Stop only when terminal is true.
  3. Poll statusUrl after Retry-After or pollAfterMs.
  4. Retry only when safeToRetry is true.
  5. Use a new key only for a newly approved write.
  • 200 Terminal
  • 202 Active
  • 400 Invalid Input
  • 401 Unauthenticated
  • 402 Billing Required
  • 403 Account Blocked
  • 409 Idempotency conflict
  • 422 Write Rejected
  • 429 Rate Limited
  • 500 Write Failed
  • 503 Write Unavailable
The action reached success, failed, or expired. Trust terminal, result, billing, and nextAction over the HTTP class.
{
  "object": "x_write_action",
  "id": "xwa_01JY6V7F8M9N0P1Q2R3S4T5U6V",
  "status": "success",
  "terminal": true,
  "safeToRetry": false,
  "billing": { "status": "charged", "chargedCredits": "10" },
  "result": { "confirmed": true },
  "nextAction": null
}
string
Always x_write_action.
string
Durable action ID.
string
Compatibility alias for id.
string
Exact write operation.
string
Current lifecycle status.
boolean
Whether polling can stop.
boolean
Whether a later attempt could succeed.
boolean
Whether a new attempt is safe.
string
Relative polling URL.
number | null
Recommended polling delay.
boolean
Whether billing settled as charged.
string
Settled credits charged.
object
Planned and settled billing state.
object
Stable hash and exact sanitized payload.
object
Exact connected account selected.
object | null
Exact target type and ID.
string | null
Compatibility target ID.
object | null
Confirmed result or desired state.
object | null
Required poll, retry, or verification step.
string
Stable request fingerprint.
string
Correlation ID.
boolean
Whether this response replayed an existing action.
string
Machine-readable error code.
string
Actionable status or error message.
boolean
Whether dispatch occurred.
string
ISO 8601 dispatch time.
string
ISO 8601 creation time.
string
ISO 8601 latest update time.
string
ISO 8601 terminal time.
string
Nonterminal resolution deadline.
string
ISO 8601 confirmation time.
string
ISO 8601 latest confirmation check.
number
Confirmation attempt count.
string
Confirmed tweet ID when available.
string
Confirmed direct message ID when available.
string
Confirmed media ID when available.
string
Public media URL when available.
string
Confirmed community ID when available.
string
Confirmed community name when available.
string
Compatibility result ID.
object
Media details when used.
object
Structured recovery context.
boolean
Whether status is success.
Last modified on July 21, 2026
Morty Proxy This is a proxified and sanitized view of the page, visit original site.