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
curl https://upload.ad/api/v1/creatives/cr_1/copy \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
C#
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);
json
{
	"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.

FieldTypeDescription
headlinestring, optionalUp to 500 characters
primaryTextstring, optionalUp to 5000 characters
descriptionstring, optionalUp to 1000 characters
labelstring, optionalYour own tag for the variant, up to 100 characters
cURL
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" }'
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
C#
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
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" }'
JavaScript
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' })
});
Python
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
<?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);
Go
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()
}
Ruby
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) }
Java
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());
	}
}
C#
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
curl -X DELETE https://upload.ad/api/v1/creatives/cr_1/copy/cp_1 \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
await fetch('https://upload.ad/api/v1/creatives/cr_1/copy/cp_1', {
	method: 'DELETE',
	headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
Python
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
<?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);
Go
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()
}
Ruby
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) }
Java
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());
	}
}
C#
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());
json
{ "ok": true }