Copy variants
A copy variant is a set of ad texts (headline, primary text, description) attached to a creative. All fields are optional, so a variant can hold as little as a single headline.
GET/api/v1/creatives/{id}/copy
List copy variants for a creative, newest first, cursor-paginated. limit caps the page size (default 100, max 200); pass nextCursor from one page as cursor for the next. Returns 404 if the creative does not exist.
curl https://upload.ad/api/v1/creatives/cr_1/copy \
-H "Authorization: Bearer $UPLOADAD_API_KEY"const res = await fetch('https://upload.ad/api/v1/creatives/cr_1/copy', {
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
const { copies, nextCursor } = await res.json();import os, requests
res = requests.get(
"https://upload.ad/api/v1/creatives/cr_1/copy",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)
copies = res.json()["copies"]<?php
$ch = curl_init('https://upload.ad/api/v1/creatives/cr_1/copy');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
$copies = json_decode(curl_exec($ch), true)['copies'];
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://upload.ad/api/v1/creatives/cr_1/copy", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require "net/http"
require "json"
uri = URI("https://upload.ad/api/v1/creatives/cr_1/copy")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
copies = JSON.parse(res.body)["copies"]import java.net.URI;
import java.net.http.*;
public class ListCopy {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/creatives/cr_1/copy"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("UPLOADAD_API_KEY")}");
var body = await client.GetStringAsync("https://upload.ad/api/v1/creatives/cr_1/copy");
Console.WriteLine(body);{
"copies": [
{
"id": "cp_1",
"headline": "Summer sale",
"primaryText": "Save 20% this week only.",
"description": "Limited time offer",
"label": "variant-a",
"createdAt": "2026-07-09T10:20:00.000Z"
}
],
"nextCursor": null
}POST/api/v1/creatives/{id}/copy
Create a copy variant. Returns 201 with { "id": "..." }. To change an existing variant, use PATCH below.
| Field | Type | Description |
|---|---|---|
headline | string, optional | Up to 500 characters |
primaryText | string, optional | Up to 5000 characters |
description | string, optional | Up to 1000 characters |
label | string, optional | Your own tag for the variant, up to 100 characters |
curl -X POST https://upload.ad/api/v1/creatives/cr_1/copy \
-H "Authorization: Bearer $UPLOADAD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "headline": "Summer sale", "primaryText": "Save 20% this week only.", "label": "variant-a" }'const res = await fetch('https://upload.ad/api/v1/creatives/cr_1/copy', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
headline: 'Summer sale',
primaryText: 'Save 20% this week only.',
label: 'variant-a'
})
});
const { id } = await res.json();import os, requests
res = requests.post(
"https://upload.ad/api/v1/creatives/cr_1/copy",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
json={
"headline": "Summer sale",
"primaryText": "Save 20% this week only.",
"label": "variant-a",
},
)
copy_id = res.json()["id"]<?php
$ch = curl_init('https://upload.ad/api/v1/creatives/cr_1/copy');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . getenv('UPLOADAD_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'headline' => 'Summer sale',
'primaryText' => 'Save 20% this week only.',
'label' => 'variant-a',
]));
$id = json_decode(curl_exec($ch), true)['id'];
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{ "headline": "Summer sale", "primaryText": "Save 20% this week only.", "label": "variant-a" }`)
req, _ := http.NewRequest("POST", "https://upload.ad/api/v1/creatives/cr_1/copy", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}require "net/http"
require "json"
uri = URI("https://upload.ad/api/v1/creatives/cr_1/copy")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
req["Content-Type"] = "application/json"
req.body = { headline: "Summer sale", primaryText: "Save 20% this week only.", label: "variant-a" }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
id = JSON.parse(res.body)["id"]import java.net.URI;
import java.net.http.*;
public class CreateCopy {
public static void main(String[] args) throws Exception {
String body = "{ \"headline\": \"Summer sale\", \"primaryText\": \"Save 20% this week only.\", \"label\": \"variant-a\" }";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/creatives/cr_1/copy"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("UPLOADAD_API_KEY")}");
var content = new StringContent(
"{ \"headline\": \"Summer sale\", \"primaryText\": \"Save 20% this week only.\", \"label\": \"variant-a\" }",
Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://upload.ad/api/v1/creatives/cr_1/copy", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());PATCH/api/v1/creatives/{id}/copy/{copyId}
Update a copy variant. A patch: only the fields you send are overwritten, so a single-field update leaves the others intact. Takes the same fields as create; returns { "id": "..." }, or 404 if the variant does not exist.
curl -X PATCH https://upload.ad/api/v1/creatives/cr_1/copy/cp_1 \
-H "Authorization: Bearer $UPLOADAD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "headline": "Summer sale, extended" }'await fetch('https://upload.ad/api/v1/creatives/cr_1/copy/cp_1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ headline: 'Summer sale, extended' })
});import os, requests
requests.patch(
"https://upload.ad/api/v1/creatives/cr_1/copy/cp_1",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
json={"headline": "Summer sale, extended"},
)<?php
$ch = curl_init('https://upload.ad/api/v1/creatives/cr_1/copy/cp_1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . getenv('UPLOADAD_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['headline' => 'Summer sale, extended']));
curl_exec($ch);
curl_close($ch);package main
import (
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{ "headline": "Summer sale, extended" }`)
req, _ := http.NewRequest("PATCH", "https://upload.ad/api/v1/creatives/cr_1/copy/cp_1", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
res.Body.Close()
}require "net/http"
require "json"
uri = URI("https://upload.ad/api/v1/creatives/cr_1/copy/cp_1")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
req["Content-Type"] = "application/json"
req.body = { headline: "Summer sale, extended" }.to_json
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }import java.net.URI;
import java.net.http.*;
public class UpdateCopy {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/creatives/cr_1/copy/cp_1"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.header("Content-Type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString("{ \"headline\": \"Summer sale, extended\" }"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("UPLOADAD_API_KEY")}");
var req = new HttpRequestMessage(HttpMethod.Patch, "https://upload.ad/api/v1/creatives/cr_1/copy/cp_1")
{
Content = new StringContent("{ \"headline\": \"Summer sale, extended\" }", Encoding.UTF8, "application/json")
};
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());DELETE/api/v1/creatives/{id}/copy/{copyId}
Delete a copy variant. Deleting an already-deleted variant succeeds, so retries are safe.
curl -X DELETE https://upload.ad/api/v1/creatives/cr_1/copy/cp_1 \
-H "Authorization: Bearer $UPLOADAD_API_KEY"await fetch('https://upload.ad/api/v1/creatives/cr_1/copy/cp_1', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});import os, requests
requests.delete(
"https://upload.ad/api/v1/creatives/cr_1/copy/cp_1",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)<?php
$ch = curl_init('https://upload.ad/api/v1/creatives/cr_1/copy/cp_1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
curl_exec($ch);
curl_close($ch);package main
import (
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://upload.ad/api/v1/creatives/cr_1/copy/cp_1", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
res, _ := http.DefaultClient.Do(req)
res.Body.Close()
}require "net/http"
uri = URI("https://upload.ad/api/v1/creatives/cr_1/copy/cp_1")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }import java.net.URI;
import java.net.http.*;
public class DeleteCopy {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/creatives/cr_1/copy/cp_1"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.DELETE()
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("UPLOADAD_API_KEY")}");
var res = await client.DeleteAsync("https://upload.ad/api/v1/creatives/cr_1/copy/cp_1");
Console.WriteLine(await res.Content.ReadAsStringAsync());{ "ok": true }