curl --request POST \
--url https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"tags": [
"<string>"
],
"selectedPostIds": [
"<string>"
],
"query": "<string>",
"filters": "<string>"
}
'import requests
url = "https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags"
payload = {
"tags": ["<string>"],
"selectedPostIds": ["<string>"],
"query": "<string>",
"filters": "<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({
tags: ['<string>'],
selectedPostIds: ['<string>'],
query: '<string>',
filters: '<string>'
})
};
fetch('https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags', 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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags",
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([
'tags' => [
'<string>'
],
'selectedPostIds' => [
'<string>'
],
'query' => '<string>',
'filters' => '<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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags"
payload := strings.NewReader("{\n \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags")
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 \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"success": true,
"requestedCount": 123,
"updatedCount": 123,
"capped": true,
"tags": [
"<string>"
],
"operation": "add",
"results": [
{
"postId": "<string>",
"companyId": "<string>",
"analyticsTags": [
"<string>"
]
}
],
"errors": [
{
"postId": "<string>",
"companyId": "<string>",
"error": "<string>"
}
]
}
}{
"error": {
"code": "unauthorized",
"message": "Missing bearer access token",
"requestId": "req_..."
}
}{
"error": {
"code": "insufficient_scope",
"message": "Requires scope 'campaigns:write'",
"requestId": "req_..."
}
}{
"error": {
"code": "not_found",
"message": "Resource not found",
"requestId": "req_..."
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"requestId": "req_..."
}
}Bulk update analytics tags
bulk add / remove / replace analytics tags across the token tenant’s posts. Requires the posts:write scope.
curl --request POST \
--url https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"tags": [
"<string>"
],
"selectedPostIds": [
"<string>"
],
"query": "<string>",
"filters": "<string>"
}
'import requests
url = "https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags"
payload = {
"tags": ["<string>"],
"selectedPostIds": ["<string>"],
"query": "<string>",
"filters": "<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({
tags: ['<string>'],
selectedPostIds: ['<string>'],
query: '<string>',
filters: '<string>'
})
};
fetch('https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags', 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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags",
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([
'tags' => [
'<string>'
],
'selectedPostIds' => [
'<string>'
],
'query' => '<string>',
'filters' => '<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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags"
payload := strings.NewReader("{\n \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<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://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.sideshift.app/api/oauth/v1/posts/bulk-analytics-tags")
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 \"tags\": [\n \"<string>\"\n ],\n \"selectedPostIds\": [\n \"<string>\"\n ],\n \"query\": \"<string>\",\n \"filters\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"success": true,
"requestedCount": 123,
"updatedCount": 123,
"capped": true,
"tags": [
"<string>"
],
"operation": "add",
"results": [
{
"postId": "<string>",
"companyId": "<string>",
"analyticsTags": [
"<string>"
]
}
],
"errors": [
{
"postId": "<string>",
"companyId": "<string>",
"error": "<string>"
}
]
}
}{
"error": {
"code": "unauthorized",
"message": "Missing bearer access token",
"requestId": "req_..."
}
}{
"error": {
"code": "insufficient_scope",
"message": "Requires scope 'campaigns:write'",
"requestId": "req_..."
}
}{
"error": {
"code": "not_found",
"message": "Resource not found",
"requestId": "req_..."
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"requestId": "req_..."
}
}Authorizations
OAuth 2.1 authorization code + PKCE (S256). Tokens are tenant-bound (company_id) and scoped. Discover endpoints via /.well-known/oauth-authorization-server.
Body
At least one of selectedPostIds, query or filters is required, otherwise the route returns 400 rather than tag the whole company by default.
That guard is weaker than it looks. filters satisfies it without narrowing anything, so a request whose only target is filters resolves to every post in the company - capped at the 5,000 most recently uploaded. A query of *, or any other symbol-only string, does the same, because it produces no searchable tokens and the text predicate is dropped. Narrow with selectedPostIds, or with a query containing real words.
How to apply the tags across all matched posts.
add, remove, replace Tag strings to apply (at least one after sanitization).
1Explicit post ids to target.
Free-text search over post title/creator/contractorName to select targets. Pass "*" to mean all posts.
Accepted but never read. It is a legacy wire field kept for deploy compatibility and has no replacement on this endpoint, so it does NOT narrow the target set - see the warning on this schema.
Response
Per-post results and errors for the bulk tag operation.
Show child attributes
Show child attributes