Uploads
An upload is a background job that pushes one file to your Meta or TikTok ad account. A newly created upload has status processing; poll it until it becomes completed or failed. Completed uploads also appear in your creative library.
The upload object:
| Field | Type | Description |
|---|---|---|
id | string | Upload id |
fileName | string | Original file name |
kind | string | image or video |
sizeBytes | integer | File size in bytes |
status | string | processing, completed, or failed |
error | string or null | Failure reason, set only on failed uploads |
platform | string | meta or tiktok, the ad account the file goes to |
facebook.imageHash | string or null | Graph API image hash, set on completed image uploads |
facebook.videoId | string or null | Facebook video id, set on completed video uploads |
tiktok.imageId | string or null | TikTok image id, set on completed image uploads |
tiktok.videoId | string or null | TikTok video id, set on completed video uploads |
createdAt | string | Creation time |
POST/api/v1/uploads
Upload one or more files as multipart/form-data with one or more files parts. An optional platform field (meta default, or tiktok) picks the ad account the files are pushed to. Returns 201 with one upload job per file.
Limits per request:
- Up to 25 files
- Images up to 30 MB, videos up to 200 MB
- Content type must be
image/*orvideo/*
Requires an active subscription; returns 402 otherwise.
curl -X POST https://upload.ad/api/v1/uploads \
-H "Authorization: Bearer $UPLOADAD_API_KEY" \
-F "files=@banner.png" \
-F "files=@promo.mp4"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('files', new Blob([await readFile('banner.png')], { type: 'image/png' }), 'banner.png');
const res = await fetch('https://upload.ad/api/v1/uploads', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` },
body: form
});
const { uploads } = await res.json();import os, requests
res = requests.post(
"https://upload.ad/api/v1/uploads",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
files=[("files", open("banner.png", "rb")), ("files", open("promo.mp4", "rb"))],
)
uploads = res.json()["uploads"]<?php
$ch = curl_init('https://upload.ad/api/v1/uploads');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'files' => new CURLFile('banner.png', 'image/png', 'banner.png'),
]);
$uploads = json_decode(curl_exec($ch), true)['uploads'];
curl_close($ch);package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, _ := w.CreateFormFile("files", "banner.png")
f, _ := os.Open("banner.png")
io.Copy(part, f)
f.Close()
w.Close()
req, _ := http.NewRequest("POST", "https://upload.ad/api/v1/uploads", &buf)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
req.Header.Set("Content-Type", w.FormDataContentType())
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/uploads")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
req.set_form([["files", File.open("banner.png")]], "multipart/form-data")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
uploads = JSON.parse(res.body)["uploads"]import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
public class Upload {
public static void main(String[] args) throws Exception {
String boundary = "ua-boundary";
byte[] file = Files.readAllBytes(Path.of("banner.png"));
byte[] head = ("--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"files\"; filename=\"banner.png\"\r\n"
+ "Content-Type: image/png\r\n\r\n").getBytes();
byte[] tail = ("\r\n--" + boundary + "--\r\n").getBytes();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArrays(java.util.List.of(head, file, tail)))
.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")}");
using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("banner.png")), "files", "banner.png");
var res = await client.PostAsync("https://upload.ad/api/v1/uploads", form);
Console.WriteLine(await res.Content.ReadAsStringAsync());{
"uploads": [
{ "id": "up_1", "status": "processing" },
{ "id": "up_2", "status": "processing" }
]
}GET/api/v1/uploads
List upload jobs, newest first.
| Query parameter | Type | Description |
|---|---|---|
limit | integer (1 to 200) | Maximum results, default 100 |
curl "https://upload.ad/api/v1/uploads?limit=20" \
-H "Authorization: Bearer $UPLOADAD_API_KEY"const res = await fetch('https://upload.ad/api/v1/uploads?limit=20', {
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
const { uploads } = await res.json();import os, requests
res = requests.get(
"https://upload.ad/api/v1/uploads",
params={"limit": 20},
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)
uploads = res.json()["uploads"]<?php
$ch = curl_init('https://upload.ad/api/v1/uploads?limit=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
$uploads = json_decode(curl_exec($ch), true)['uploads'];
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://upload.ad/api/v1/uploads?limit=20", 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/uploads?limit=20")
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) }
uploads = JSON.parse(res.body)["uploads"]import java.net.URI;
import java.net.http.*;
public class ListUploads {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads?limit=20"))
.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/uploads?limit=20");
Console.WriteLine(body);{
"uploads": [
{
"id": "up_1",
"fileName": "banner.png",
"kind": "image",
"sizeBytes": 482113,
"status": "completed",
"error": null,
"facebook": { "imageHash": "a1b2c3...", "videoId": null },
"createdAt": "2026-07-09T10:15:00.000Z"
}
]
}GET/api/v1/uploads/{id}
Fetch a single upload job. Returns 404 if not found.
curl https://upload.ad/api/v1/uploads/up_1 \
-H "Authorization: Bearer $UPLOADAD_API_KEY"const res = await fetch('https://upload.ad/api/v1/uploads/up_1', {
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
const { upload } = await res.json();import os, requests
res = requests.get(
"https://upload.ad/api/v1/uploads/up_1",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)
upload = res.json()["upload"]<?php
$ch = curl_init('https://upload.ad/api/v1/uploads/up_1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
$upload = json_decode(curl_exec($ch), true)['upload'];
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://upload.ad/api/v1/uploads/up_1", 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/uploads/up_1")
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) }
upload = JSON.parse(res.body)["upload"]import java.net.URI;
import java.net.http.*;
public class GetUpload {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads/up_1"))
.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/uploads/up_1");
Console.WriteLine(body);{
"upload": {
"id": "up_1",
"fileName": "banner.png",
"kind": "image",
"sizeBytes": 482113,
"status": "completed",
"error": null,
"facebook": { "imageHash": "a1b2c3...", "videoId": null },
"createdAt": "2026-07-09T10:15:00.000Z"
}
}To wait for an upload to finish, poll this endpoint every few seconds, or configure a webhook to be notified instead:
ID="up_1"
while true; do
STATUS=$(curl -s "https://upload.ad/api/v1/uploads/$ID" \
-H "Authorization: Bearer $UPLOADAD_API_KEY" | jq -r .upload.status)
[ "$STATUS" != "processing" ] && break
sleep 3
done
echo "Upload $ID finished with status: $STATUS"async function waitForUpload(id) {
while (true) {
const res = await fetch(`https://upload.ad/api/v1/uploads/${id}`, {
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
const { upload } = await res.json();
if (upload.status !== 'processing') return upload;
await new Promise((r) => setTimeout(r, 3000));
}
}import os, time, requests
def wait_for_upload(upload_id):
headers = {"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"}
while True:
upload = requests.get(
f"https://upload.ad/api/v1/uploads/{upload_id}", headers=headers
).json()["upload"]
if upload["status"] != "processing":
return upload
time.sleep(3)<?php
function waitForUpload($id) {
while (true) {
$ch = curl_init("https://upload.ad/api/v1/uploads/$id");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
$upload = json_decode(curl_exec($ch), true)['upload'];
curl_close($ch);
if ($upload['status'] !== 'processing') return $upload;
sleep(3);
}
}package main
import (
"encoding/json"
"net/http"
"os"
"time"
)
func waitForUpload(id string) map[string]any {
for {
req, _ := http.NewRequest("GET", "https://upload.ad/api/v1/uploads/"+id, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("UPLOADAD_API_KEY"))
res, _ := http.DefaultClient.Do(req)
var body struct {
Upload map[string]any `json:"upload"`
}
json.NewDecoder(res.Body).Decode(&body)
res.Body.Close()
if body.Upload["status"] != "processing" {
return body.Upload
}
time.Sleep(3 * time.Second)
}
}require "net/http"
require "json"
def wait_for_upload(id)
loop do
uri = URI("https://upload.ad/api/v1/uploads/#{id}")
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) }
upload = JSON.parse(res.body)["upload"]
return upload if upload["status"] != "processing"
sleep 3
end
endimport java.net.URI;
import java.net.http.*;
public class WaitForUpload {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
while (true) {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads/up_1"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.build();
String body = client.send(req, HttpResponse.BodyHandlers.ofString()).body();
if (!body.contains("\"processing\"")) {
System.out.println(body);
break;
}
Thread.sleep(3000);
}
}
}using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("UPLOADAD_API_KEY")}");
while (true)
{
var body = await client.GetStringAsync("https://upload.ad/api/v1/uploads/up_1");
var status = JsonDocument.Parse(body).RootElement
.GetProperty("upload").GetProperty("status").GetString();
if (status != "processing") { Console.WriteLine(body); break; }
await Task.Delay(3000);
}POST/api/v1/uploads/{id}
Retry a failed upload. The job returns to processing and runs again. Returns 404 if there is no failed upload with that id.
curl -X POST https://upload.ad/api/v1/uploads/up_1 \
-H "Authorization: Bearer $UPLOADAD_API_KEY"const res = await fetch('https://upload.ad/api/v1/uploads/up_1', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
const { upload } = await res.json();import os, requests
res = requests.post(
"https://upload.ad/api/v1/uploads/up_1",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)
upload = res.json()["upload"]<?php
$ch = curl_init('https://upload.ad/api/v1/uploads/up_1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('UPLOADAD_API_KEY')]);
$upload = json_decode(curl_exec($ch), true)['upload'];
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("POST", "https://upload.ad/api/v1/uploads/up_1", 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/uploads/up_1")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['UPLOADAD_API_KEY']}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
upload = JSON.parse(res.body)["upload"]import java.net.URI;
import java.net.http.*;
public class RetryUpload {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads/up_1"))
.header("Authorization", "Bearer " + System.getenv("UPLOADAD_API_KEY"))
.POST(HttpRequest.BodyPublishers.noBody())
.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.PostAsync("https://upload.ad/api/v1/uploads/up_1", null);
Console.WriteLine(await res.Content.ReadAsStringAsync());{ "upload": { "id": "up_1", "status": "processing" } }DELETE/api/v1/uploads/{id}
Dismiss a completed or failed upload job. This removes the job from the upload list; creatives created by the job are unaffected. Returns 409 if the upload is still processing.
curl -X DELETE https://upload.ad/api/v1/uploads/up_1 \
-H "Authorization: Bearer $UPLOADAD_API_KEY"await fetch('https://upload.ad/api/v1/uploads/up_1', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});import os, requests
requests.delete(
"https://upload.ad/api/v1/uploads/up_1",
headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)<?php
$ch = curl_init('https://upload.ad/api/v1/uploads/up_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/uploads/up_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/uploads/up_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 DismissUpload {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://upload.ad/api/v1/uploads/up_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/uploads/up_1");
Console.WriteLine(await res.Content.ReadAsStringAsync());{ "ok": true }