curl --request POST \
--url https://api.superx.so/v1/datasets \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": "repliers",
"target": "https://x.com/robj3d3/status/1234567890123456789",
"title": "Repliers to my launch post",
"max_rows": 500,
"filters": {
"min_followers": 500,
"require_can_dm": true
}
}
'import requests
url = "https://api.superx.so/v1/datasets"
payload = {
"source": "repliers",
"target": "https://x.com/robj3d3/status/1234567890123456789",
"title": "Repliers to my launch post",
"max_rows": 500,
"filters": {
"min_followers": 500,
"require_can_dm": True
}
}
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({
source: 'repliers',
target: 'https://x.com/robj3d3/status/1234567890123456789',
title: 'Repliers to my launch post',
max_rows: 500,
filters: {min_followers: 500, require_can_dm: true}
})
};
fetch('https://api.superx.so/v1/datasets', 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/datasets",
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([
'source' => 'repliers',
'target' => 'https://x.com/robj3d3/status/1234567890123456789',
'title' => 'Repliers to my launch post',
'max_rows' => 500,
'filters' => [
'min_followers' => 500,
'require_can_dm' => true
]
]),
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/datasets"
payload := strings.NewReader("{\n \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\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/datasets")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.superx.so/v1/datasets")
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 \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "VKcPAVU1FSkx4kzPuehGK",
"title": "Repliers to my launch post",
"source": "repliers",
"status": "ready",
"target_ref": "1234567890123456789",
"x_account_id": "1178367350552305665",
"ask_chat_id": null,
"columns": [
"handle",
"name",
"bio",
"website",
"followers",
"following",
"location",
"can_dm",
"verified"
],
"row_count": 64,
"scanned_count": 71,
"total_estimate": 71,
"coverage_complete": true,
"has_people": true,
"created_at": "2026-09-10T10:12:00.000Z",
"expires_at": "2026-10-10T10:12:00.000Z",
"coverage": "Includes all 71 direct replies to the post.",
"filters": {
"minFollowers": 500,
"requireCanDm": true
}
}
}Collect an audience into a dataset
Collect the repliers, quote posters or reposters of a post, the members of a public X list, or the account’s own posts or replies, into a stored dataset you can then read, export, or copy into a contact list.
source: "research" is the exception: instead of walking an audience
it reads people’s recent posts and writes a short outreach brief for
each one (what they do, what they are building, recent topics,
personalization hooks that QUOTE their real posts, and an opener
angle). Give it exactly one source - handles, list_id, agent_id
or dataset_id - plus optional focus, title and max_rows (1-25,
default 10). The briefs land as an ordinary dataset, so
GET /v1/datasets/{id}/rows, /export and /contacts read them
unchanged. More than 5 profiles research in the background and answer
202. If none of them could be researched, the answer is 200 with
data: null, the reasons in meta.skipped, and every credit
returned.
This endpoint can answer before the work is finished. A small
collection runs inside the request and answers 200 with a ready
dataset. A big one, or one whose size cannot be established up
front, answers 202 with a collecting dataset: poll
GET /v1/datasets/{id} until status is ready (or failed) before
reading its rows. Only one background collection runs per account at a
time; a second one gets 409 collection_in_progress.
If nothing matched the filters, the response is 200 with
data: null and a note: no dataset is created for an empty result,
and the daily collection slot is given back.
my_posts and my_replies read the account’s own synced posts: they
take no target, always finish inside the request, and ignore the
profile filters (the response note says so rather than dropping them
silently).
Cost. Every collection counts against a limit of 10 per day for
the account, shared with the collections Ask SuperX runs in the app
(429 collection_quota_exceeded). Collections that read X also spend
the enrichment allowance: one unit for the size estimate, plus the
pages the walk is expected to need, all charged before the walk
starts so X-RateLimit-Remaining stays honest about work that
outlives the response. Own-content collections spend nothing.
Research is charged in AI credits, not collections.
source: "research" costs a flat 1 credit per profile ACTUALLY
researched: profiles that could not be researched come back in
meta.skipped and their credits are returned. It spends one of the
plan’s daily profile-research runs (429 ai_action_limited with
scope: "account") and a slice of a platform-wide fair-use ceiling on
live-data actions shared by every SuperX account
(429 ai_action_limited with scope: "platform"), and it does NOT
count against the 10 collections a day. An empty credit pool is
429 ai_credits_exhausted. What was charged comes back in
meta.credits_charged and in the X-Credits-* headers on the 200;
a background research run settles when it finishes, so its 202
carries neither, and the figure lands on GET /v1/me and your ledger.
Datasets created here appear alongside the ones Ask SuperX built, and are kept for 30 days.
Send an Idempotency-Key header to make retries safe: the same key
with the same body replays the first response with
Idempotency-Replayed: true, and the same key with a different body
is rejected with 409 idempotency_key_reuse.
curl --request POST \
--url https://api.superx.so/v1/datasets \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": "repliers",
"target": "https://x.com/robj3d3/status/1234567890123456789",
"title": "Repliers to my launch post",
"max_rows": 500,
"filters": {
"min_followers": 500,
"require_can_dm": true
}
}
'import requests
url = "https://api.superx.so/v1/datasets"
payload = {
"source": "repliers",
"target": "https://x.com/robj3d3/status/1234567890123456789",
"title": "Repliers to my launch post",
"max_rows": 500,
"filters": {
"min_followers": 500,
"require_can_dm": True
}
}
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({
source: 'repliers',
target: 'https://x.com/robj3d3/status/1234567890123456789',
title: 'Repliers to my launch post',
max_rows: 500,
filters: {min_followers: 500, require_can_dm: true}
})
};
fetch('https://api.superx.so/v1/datasets', 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/datasets",
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([
'source' => 'repliers',
'target' => 'https://x.com/robj3d3/status/1234567890123456789',
'title' => 'Repliers to my launch post',
'max_rows' => 500,
'filters' => [
'min_followers' => 500,
'require_can_dm' => true
]
]),
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/datasets"
payload := strings.NewReader("{\n \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\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/datasets")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.superx.so/v1/datasets")
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 \"source\": \"repliers\",\n \"target\": \"https://x.com/robj3d3/status/1234567890123456789\",\n \"title\": \"Repliers to my launch post\",\n \"max_rows\": 500,\n \"filters\": {\n \"min_followers\": 500,\n \"require_can_dm\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "VKcPAVU1FSkx4kzPuehGK",
"title": "Repliers to my launch post",
"source": "repliers",
"status": "ready",
"target_ref": "1234567890123456789",
"x_account_id": "1178367350552305665",
"ask_chat_id": null,
"columns": [
"handle",
"name",
"bio",
"website",
"followers",
"following",
"location",
"can_dm",
"verified"
],
"row_count": 64,
"scanned_count": 71,
"total_estimate": 71,
"coverage_complete": true,
"has_people": true,
"created_at": "2026-09-10T10:12:00.000Z",
"expires_at": "2026-10-10T10:12:00.000Z",
"coverage": "Includes all 71 direct replies to the post.",
"filters": {
"minFollowers": 500,
"requireCanDm": true
}
}
}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
Who to collect. repliers, quoters and reposters need a post target; list_members needs a public X list; my_posts and my_replies read the account's own synced posts and take no target.
research is different: it reads people's recent posts and writes
an outreach brief per person instead of walking an audience. It
takes exactly ONE of handles, list_id, agent_id or
dataset_id, plus focus and a max_rows of 1-25 (default 10),
and ignores target and filters.
repliers, quoters, reposters, list_members, my_replies, my_posts, research An x.com post URL or numeric post id, or for list_members an x.com list URL (/i/lists/) or numeric list id. Omit for my_posts and my_replies.
Title for the dataset. A sensible one is generated when omitted.
120Rows to collect at most. For source: "research" the range is
1-25 and the default is 10 (one profile per row).
1 <= x <= 1000Which of your accounts to collect as. Omit for the main account.
research only: research these X handles (with or without the @). Exactly one source field.
25research only: research the members of this contact list. Exactly one source field.
research only: research this signal agent's leads. Exactly one source field.
x >= 1research only: research the people in this dataset. Exactly one source field.
research only: an optional steer, e.g. "founders who might need audience-growth tooling".
300Show child attributes
Show child attributes
Response
The collection finished inside the request. data is null when
nothing matched, with a note explaining why. A research run
adds meta (what was requested, what was researched, what it
cost) and the X-Credits-* headers.