Bulk add ghost videos to a campaign
curl --request POST \
--url https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"videos": [
{
"ghostName": "Jane Creator",
"platform": "tiktok",
"videoUrl": "https://www.tiktok.com/@jane/video/123"
},
{
"ghostName": "Brand Clip",
"videoUrl": "https://www.instagram.com/reel/abc123/"
}
]
}
'import requests
url = "https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk"
payload = { "videos": [
{
"ghostName": "Jane Creator",
"platform": "tiktok",
"videoUrl": "https://www.tiktok.com/@jane/video/123"
},
{
"ghostName": "Brand Clip",
"videoUrl": "https://www.instagram.com/reel/abc123/"
}
] }
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({
videos: [
{
ghostName: 'Jane Creator',
platform: 'tiktok',
videoUrl: 'https://www.tiktok.com/@jane/video/123'
},
{ghostName: 'Brand Clip', videoUrl: 'https://www.instagram.com/reel/abc123/'}
]
})
};
fetch('https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk', 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/campaigns/{id}/ghost-videos/bulk",
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([
'videos' => [
[
'ghostName' => 'Jane Creator',
'platform' => 'tiktok',
'videoUrl' => 'https://www.tiktok.com/@jane/video/123'
],
[
'ghostName' => 'Brand Clip',
'videoUrl' => 'https://www.instagram.com/reel/abc123/'
]
]
]),
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/campaigns/{id}/ghost-videos/bulk"
payload := strings.NewReader("{\n \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\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://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk")
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 \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"programId": "<string>",
"summary": {
"totalVideos": 1,
"created": 1,
"skipped": 1,
"errors": 1
},
"results": [
{
"ghostName": "<string>",
"platform": "<string>",
"videoUrl": "<string>",
"reason": "<string>",
"contractId": "<string>"
}
],
"groupedByGhost": {},
"parseErrors": [
"<string>"
]
}
}{
"error": {
"code": "invalid_request",
"message": "name is required",
"requestId": "req_..."
}
}{
"error": {
"code": "unauthorized",
"message": "Missing bearer access token",
"requestId": "req_..."
}
}{
"error": {
"code": "subscription_required",
"message": "The company does not have an active subscription",
"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": "idempotency_conflict",
"message": "Idempotency-Key was reused with a different request body",
"requestId": "req_..."
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"requestId": "req_..."
}
}Bulk add ghost videos to a campaign
Add 1–500 tracked social videos to any campaign from structured JSON. Platform may be omitted when it can be detected from the URL. Existing video URLs are skipped and reported per row. Requires the campaigns:write scope.
POST
/
campaigns
/
{id}
/
ghost-videos
/
bulk
Bulk add ghost videos to a campaign
curl --request POST \
--url https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"videos": [
{
"ghostName": "Jane Creator",
"platform": "tiktok",
"videoUrl": "https://www.tiktok.com/@jane/video/123"
},
{
"ghostName": "Brand Clip",
"videoUrl": "https://www.instagram.com/reel/abc123/"
}
]
}
'import requests
url = "https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk"
payload = { "videos": [
{
"ghostName": "Jane Creator",
"platform": "tiktok",
"videoUrl": "https://www.tiktok.com/@jane/video/123"
},
{
"ghostName": "Brand Clip",
"videoUrl": "https://www.instagram.com/reel/abc123/"
}
] }
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({
videos: [
{
ghostName: 'Jane Creator',
platform: 'tiktok',
videoUrl: 'https://www.tiktok.com/@jane/video/123'
},
{ghostName: 'Brand Clip', videoUrl: 'https://www.instagram.com/reel/abc123/'}
]
})
};
fetch('https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk', 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/campaigns/{id}/ghost-videos/bulk",
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([
'videos' => [
[
'ghostName' => 'Jane Creator',
'platform' => 'tiktok',
'videoUrl' => 'https://www.tiktok.com/@jane/video/123'
],
[
'ghostName' => 'Brand Clip',
'videoUrl' => 'https://www.instagram.com/reel/abc123/'
]
]
]),
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/campaigns/{id}/ghost-videos/bulk"
payload := strings.NewReader("{\n \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\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://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.sideshift.app/api/oauth/v1/campaigns/{id}/ghost-videos/bulk")
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 \"videos\": [\n {\n \"ghostName\": \"Jane Creator\",\n \"platform\": \"tiktok\",\n \"videoUrl\": \"https://www.tiktok.com/@jane/video/123\"\n },\n {\n \"ghostName\": \"Brand Clip\",\n \"videoUrl\": \"https://www.instagram.com/reel/abc123/\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"programId": "<string>",
"summary": {
"totalVideos": 1,
"created": 1,
"skipped": 1,
"errors": 1
},
"results": [
{
"ghostName": "<string>",
"platform": "<string>",
"videoUrl": "<string>",
"reason": "<string>",
"contractId": "<string>"
}
],
"groupedByGhost": {},
"parseErrors": [
"<string>"
]
}
}{
"error": {
"code": "invalid_request",
"message": "name is required",
"requestId": "req_..."
}
}{
"error": {
"code": "unauthorized",
"message": "Missing bearer access token",
"requestId": "req_..."
}
}{
"error": {
"code": "subscription_required",
"message": "The company does not have an active subscription",
"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": "idempotency_conflict",
"message": "Idempotency-Key was reused with a different request body",
"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.
Headers
Client-chosen key; replaying the same key + body returns the original response, a different body returns 409.
Path Parameters
Body
application/json
Required array length:
1 - 500 elementsShow child attributes
Show child attributes
Response
Per-video creation, skip, and error results.
Show child attributes
Show child attributes
⌘I