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:

FieldTypeDescription
idstringUpload id
fileNamestringOriginal file name
kindstringimage or video
sizeBytesintegerFile size in bytes
statusstringprocessing, completed, or failed
errorstring or nullFailure reason, set only on failed uploads
platformstringmeta or tiktok, the ad account the file goes to
facebook.imageHashstring or nullGraph API image hash, set on completed image uploads
facebook.videoIdstring or nullFacebook video id, set on completed video uploads
tiktok.imageIdstring or nullTikTok image id, set on completed image uploads
tiktok.videoIdstring or nullTikTok video id, set on completed video uploads
createdAtstringCreation 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/* or video/*

Requires an active subscription; returns 402 otherwise.

cURL
curl -X POST https://upload.ad/api/v1/uploads \
  -H "Authorization: Bearer $UPLOADAD_API_KEY" \
  -F "files=@banner.png" \
  -F "files=@promo.mp4"
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
C#
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());
json
{
	"uploads": [
		{ "id": "up_1", "status": "processing" },
		{ "id": "up_2", "status": "processing" }
	]
}

GET/api/v1/uploads

List upload jobs, newest first.

Query parameterTypeDescription
limitinteger (1 to 200)Maximum results, default 100
cURL
curl "https://upload.ad/api/v1/uploads?limit=20" \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
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/uploads?limit=20");
Console.WriteLine(body);
json
{
	"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
curl https://upload.ad/api/v1/uploads/up_1 \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
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/uploads/up_1");
Console.WriteLine(body);
json
{
	"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:

cURL
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"
JavaScript
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));
	}
}
Python
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
<?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);
    }
}
Go
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)
	}
}
Ruby
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
end
Java
import 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);
		}
	}
}
C#
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
curl -X POST https://upload.ad/api/v1/uploads/up_1 \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
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();
Python
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
<?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);
Go
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))
}
Ruby
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"]
Java
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());
	}
}
C#
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());
json
{ "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
curl -X DELETE https://upload.ad/api/v1/uploads/up_1 \
  -H "Authorization: Bearer $UPLOADAD_API_KEY"
JavaScript
await fetch('https://upload.ad/api/v1/uploads/up_1', {
	method: 'DELETE',
	headers: { Authorization: `Bearer ${process.env.UPLOADAD_API_KEY}` }
});
Python
import os, requests

requests.delete(
    "https://upload.ad/api/v1/uploads/up_1",
    headers={"Authorization": f"Bearer {os.environ['UPLOADAD_API_KEY']}"},
)
PHP
<?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);
Go
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()
}
Ruby
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) }
Java
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());
	}
}
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/uploads/up_1");
Console.WriteLine(await res.Content.ReadAsStringAsync());
json
{ "ok": true }