curl --request POST \
--url https://api.superx.so/v1/dm/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"recipients": [
{
"x_user_id": "44196397",
"handle": "levelsio",
"name": "Pieter Levels"
}
],
"message": "Hey [first], loved your thread on shipping fast."
}
'import requests
url = "https://api.superx.so/v1/dm/campaigns"
payload = {
"recipients": [
{
"x_user_id": "44196397",
"handle": "levelsio",
"name": "Pieter Levels"
}
],
"message": "Hey [first], loved your thread on shipping fast."
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
recipients: [{x_user_id: '44196397', handle: 'levelsio', name: 'Pieter Levels'}],
message: 'Hey [first], loved your thread on shipping fast.'
})
};
fetch('https://api.superx.so/v1/dm/campaigns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.superx.so/v1/dm/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'recipients' => [
[
'x_user_id' => '44196397',
'handle' => 'levelsio',
'name' => 'Pieter Levels'
]
],
'message' => 'Hey [first], loved your thread on shipping fast.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.superx.so/v1/dm/campaigns"
payload := strings.NewReader("{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.superx.so/v1/dm/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.superx.so/v1/dm/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": null,
"queued": 0,
"queued_now": 0,
"scheduled": [],
"scheduled_total": 0,
"skipped": 2,
"duplicates": 2,
"remaining_this_month": 450
},
"note": "Every recipient was skipped (already queued in the last 24 hours, over quota, or the sender); nothing was queued."
}{
"data": {
"id": "V1StGXR8_Z5jdHi6B-myT",
"queued": 2,
"queued_now": 2,
"scheduled": [],
"scheduled_total": 0,
"skipped": 0,
"duplicates": 0,
"remaining_this_month": 448
},
"note": "Queued into your DM pipeline. Nothing is sent by this call; the scheduler sends within your daily and monthly limits. You are responsible for these messages under X's automation rules."
}{
"error": {
"code": "account_not_linked",
"message": "This account has no X account connected, so it cannot send DMs."
}
}{
"error": {
"code": "invalid_api_key",
"message": "Unknown or revoked API key"
}
}{
"error": {
"code": "dm_not_in_plan",
"message": "This account's plan does not include DM sending."
}
}{
"error": {
"code": "account_not_found",
"message": "No account with that id belongs to this key"
}
}{
"error": {
"code": "idempotency_key_reuse",
"message": "This Idempotency-Key was already used with a different request body."
}
}{
"error": {
"code": "dm_limit_reached",
"message": "This account's monthly DM allowance is used up.",
"scope": "month",
"retry_after": 86400,
"reset_at": "2026-10-01T00:00:00.000Z"
}
}{
"error": {
"code": "internal_error",
"message": "Failed to fetch posts"
}
}{
"error": {
"code": "upstream_error",
"message": "Failed to fetch scheduled posts. Try again shortly."
}
}{
"error": {
"code": "upstream_unavailable",
"message": "The scheduling service is temporarily unavailable. Retry with the same Idempotency-Key."
}
}Queue a DM campaign
Queues direct messages to up to 100 X users.
NOTHING IS SENT BY THIS CALL. The messages go into the account’s own
DM queue, the same one the SuperX app fills, and the app’s scheduler
sends them within the account’s daily and monthly DM limits. The 201
is therefore COUNTS, not deliveries: read
GET /v1/dm/campaigns/{id} to see what has actually gone out.
[name], [first] and [handle] in the message are filled in per
recipient. A recipient may carry its own message, which wins over
the shared one; when every recipient has one, the top-level message
may be omitted.
People this account already messaged in the last 24 hours are skipped
and counted in duplicates, and the sending account is never
messaged. spread: false (the default) queues only what today’s
remaining daily allowance holds and skips the rest; spread: true
places the rest over the coming days, up to 30 days ahead.
When EVERY recipient is skipped, nothing is queued: the answer is
200 with data.id: null and a note saying so, rather than a 201
carrying an id that would resolve to nothing.
Costs no AI credits. It counts against the writes rate-limit bucket
and against the account’s own DM allowances
(GET /v1/dm/limits). Takes up to 30 seconds.
With an Idempotency-Key, the campaign id IS the reservation id, so a
retry after a timeout converges on the same campaign instead of
orphaning the rows the first attempt queued.
You are responsible for these messages under X’s rules on automation and unsolicited contact.
curl --request POST \
--url https://api.superx.so/v1/dm/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"recipients": [
{
"x_user_id": "44196397",
"handle": "levelsio",
"name": "Pieter Levels"
}
],
"message": "Hey [first], loved your thread on shipping fast."
}
'import requests
url = "https://api.superx.so/v1/dm/campaigns"
payload = {
"recipients": [
{
"x_user_id": "44196397",
"handle": "levelsio",
"name": "Pieter Levels"
}
],
"message": "Hey [first], loved your thread on shipping fast."
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
recipients: [{x_user_id: '44196397', handle: 'levelsio', name: 'Pieter Levels'}],
message: 'Hey [first], loved your thread on shipping fast.'
})
};
fetch('https://api.superx.so/v1/dm/campaigns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.superx.so/v1/dm/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'recipients' => [
[
'x_user_id' => '44196397',
'handle' => 'levelsio',
'name' => 'Pieter Levels'
]
],
'message' => 'Hey [first], loved your thread on shipping fast.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.superx.so/v1/dm/campaigns"
payload := strings.NewReader("{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.superx.so/v1/dm/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.superx.so/v1/dm/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"recipients\": [\n {\n \"x_user_id\": \"44196397\",\n \"handle\": \"levelsio\",\n \"name\": \"Pieter Levels\"\n }\n ],\n \"message\": \"Hey [first], loved your thread on shipping fast.\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": null,
"queued": 0,
"queued_now": 0,
"scheduled": [],
"scheduled_total": 0,
"skipped": 2,
"duplicates": 2,
"remaining_this_month": 450
},
"note": "Every recipient was skipped (already queued in the last 24 hours, over quota, or the sender); nothing was queued."
}{
"data": {
"id": "V1StGXR8_Z5jdHi6B-myT",
"queued": 2,
"queued_now": 2,
"scheduled": [],
"scheduled_total": 0,
"skipped": 0,
"duplicates": 0,
"remaining_this_month": 448
},
"note": "Queued into your DM pipeline. Nothing is sent by this call; the scheduler sends within your daily and monthly limits. You are responsible for these messages under X's automation rules."
}{
"error": {
"code": "account_not_linked",
"message": "This account has no X account connected, so it cannot send DMs."
}
}{
"error": {
"code": "invalid_api_key",
"message": "Unknown or revoked API key"
}
}{
"error": {
"code": "dm_not_in_plan",
"message": "This account's plan does not include DM sending."
}
}{
"error": {
"code": "account_not_found",
"message": "No account with that id belongs to this key"
}
}{
"error": {
"code": "idempotency_key_reuse",
"message": "This Idempotency-Key was already used with a different request body."
}
}{
"error": {
"code": "dm_limit_reached",
"message": "This account's monthly DM allowance is used up.",
"scope": "month",
"retry_after": 86400,
"reset_at": "2026-10-01T00:00:00.000Z"
}
}{
"error": {
"code": "internal_error",
"message": "Failed to fetch posts"
}
}{
"error": {
"code": "upstream_error",
"message": "Failed to fetch scheduled posts. Try again shortly."
}
}{
"error": {
"code": "upstream_unavailable",
"message": "The scheduling service is temporarily unavailable. Retry with the same Idempotency-Key."
}
}Authorizations
A SuperX API key ("sxk_..."), created in the SuperX app under Account > API / MCP / CLI. Keys are server-side secrets.
Headers
Unique key (max 64 characters) for safe retries. Replays carry the "Idempotency-Replayed" response header set to "true". Keys are retained for 24 hours.
64Body
1 - 100 elementsShow child attributes
Show child attributes
The shared message. Required unless every recipient carries its own.
1 - 1000Spread what today's daily allowance cannot hold over the coming days instead of skipping it. Ignored when GET /v1/dm/limits reports scheduling_enabled: false; the overflow is then counted in skipped.
Any account you own, meaning your main account (the default when omitted) or one linked to it. An account shared with you returns 403 writes_main_account_only.