Count which selected topic clusters a bulk action applies to
curl --request POST \
--url https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"selection": {
"mode": "ids",
"ids": [
"<string>"
]
},
"teamId": "<string>"
}
'import requests
url = "https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview"
payload = {
"selection": {
"mode": "ids",
"ids": ["<string>"]
},
"teamId": "<string>"
}
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({selection: {mode: 'ids', ids: ['<string>']}, teamId: '<string>'})
};
fetch('https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview', 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.snowseo.com/v3/topic-clusters/topics/bulk-preview",
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([
'selection' => [
'mode' => 'ids',
'ids' => [
'<string>'
]
],
'teamId' => '<string>'
]),
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.snowseo.com/v3/topic-clusters/topics/bulk-preview"
payload := strings.NewReader("{\n \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\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.snowseo.com/v3/topic-clusters/topics/bulk-preview")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview")
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 \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"eligibility": {
"selected": 123,
"eligible": 123,
"skipped": 123,
"byReason": {}
}
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}Topic Clusters
Bulk topic cluster actions
Preview, track, untrack, or delete many topic clusters at once.
POST
/
v3
/
topic-clusters
/
topics
/
bulk-preview
Count which selected topic clusters a bulk action applies to
curl --request POST \
--url https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"selection": {
"mode": "ids",
"ids": [
"<string>"
]
},
"teamId": "<string>"
}
'import requests
url = "https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview"
payload = {
"selection": {
"mode": "ids",
"ids": ["<string>"]
},
"teamId": "<string>"
}
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({selection: {mode: 'ids', ids: ['<string>']}, teamId: '<string>'})
};
fetch('https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview', 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.snowseo.com/v3/topic-clusters/topics/bulk-preview",
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([
'selection' => [
'mode' => 'ids',
'ids' => [
'<string>'
]
],
'teamId' => '<string>'
]),
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.snowseo.com/v3/topic-clusters/topics/bulk-preview"
payload := strings.NewReader("{\n \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\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.snowseo.com/v3/topic-clusters/topics/bulk-preview")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.snowseo.com/v3/topic-clusters/topics/bulk-preview")
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 \"selection\": {\n \"mode\": \"ids\",\n \"ids\": [\n \"<string>\"\n ]\n },\n \"teamId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"eligibility": {
"selected": 123,
"eligible": 123,
"skipped": 123,
"byReason": {}
}
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}Every bulk action takes a
Each call processes one batch of eligible clusters and returns
selection and works through it in batches:
selectionis either{ mode: "ids", ids: [topicId, ...] }or{ mode: "all", filters: { search? }, excludedIds: [...] }(every cluster matching the search, minus the excluded ids).- Call
POST /v3/topic-clusters/topics/bulk-previewwith theselectionand anactionfirst. It changes nothing and returnseligibility:{ selected, eligible, skipped, byReason }, e.g. clusters skipped because they are empty or already tracked. POST /v3/topic-clusters/topics/selectionreturns the clusters a selection matches.
| Action | Endpoint | Rows per call | Notes |
|---|---|---|---|
| Track | POST /v3/topic-clusters/topics/bulk-track | 10 | Tracks every keyword and prompt in each cluster; uses keyword-tracking and AI credits. A cluster that runs out fails on its own. |
| Untrack | POST /v3/topic-clusters/topics/bulk-untrack | 10 | History is kept. |
| Delete | POST /v3/topic-clusters/topics/bulk-delete | 50 | Permanent. Planned content items in a deleted cluster are deleted with it. |
{ success, succeeded, failed, nextCursor, eligibility, results }. While nextCursor is not null, call again with the same selection and afterId set to nextCursor. results has one entry per cluster with id, name, success and error when it failed. Track and untrack results also carry trackedKeywordsCreated, trackedKeywordsUpdated and promptsSkipped (prompts left untracked for lack of credits).
For a single cluster, use Delete or the cluster’s tracking endpoint.Authorizations
bearerAuthapiKey
Your SnowSEO API key (sk_...).
Body
application/json
Response
Default Response
Show child attributes
Show child attributes
Was this page helpful?

