X Ads API media upload: simple for images, chunked for all media
X's Ads API lists POST media/upload for images only and a chunked upload for all media. Use the chunked route for video you render with Sume.

X's Creatives documentation lists POST media/upload for images only and POST media/upload (chunked) for all media. Sume returns media as media.sume.com URLs, so the handoff is: download the finished file, then send it to X with the route that fits its type.
Which route for which file
| Endpoint | Covers |
|---|---|
POST media/upload | Images only |
POST media/upload (chunked) | All media |
POST accounts/:account_id/cards | Create a card |
POST accounts/:account_id/tweets | Create a Tweet |
Fetch the Sume file and size-check it
The upload call itself needs OAuth 1.0a signing against X, which this post does not cover. The script prepares the file and picks the route from the extension and size, using the 3 MB image cap and 500 MB video cap from the same page. Replace the URL with a Sume media URL from a finished job.
import os, requests
def fetch(url: str, path: str) -> int:
data = requests.get(url, timeout=120).content
open(path, "wb").write(data)
return len(data)
def route(path: str, size: int) -> str:
ext = path.rsplit(".", 1)[-1].lower()
if ext in {"bmp", "jpeg", "jpg", "png"}:
return "media/upload" if size <= 3 * 1024 * 1024 else "too large for an image card"
if ext in {"mp4", "mov"}:
return "media/upload (chunked)" if size <= 500 * 1024 * 1024 else "over 500 MB"
return "unsupported type"
url = os.environ.get("SUME_MEDIA_URL")
if not url:
raise SystemExit("set SUME_MEDIA_URL to a media.sume.com file URL")
path = "creative." + url.rsplit(".", 1)[-1]
print(route(path, fetch(url, path)))Before you upload
- Video: the page gives a 10-minute and 500 MB ceiling, mp4 or mov.
- Trim long clips first with Sume's video trim if they run past a card's length limit.
Sources
Related posts
More in Developers
- Grok Imagine video extension vs chaining clips on Sume
xAI extends a Grok Imagine clip from its final frame. Sume has no extend call for Grok: save the last frame and submit it as first_frame on the next job.
- xargs -P 3: send a prompt file to Wan 3.0 without losing quotes
A 17-line shell script fans a prompts.txt out to Sume /v1/videos with xargs -P 3. Six 2 s 480p Wan 3.0 jobs cost $0.75, and a rerun reuses each prompt's key.
- Your backend in front of Sume video: return 202 and a job id
A 30-line Node server that submits a Sume video job with POST /v1/videos and answers your browser 202 with a job id, instead of holding the request open.
- YouTube 'daily custom thumbnail limit reached': retry in 24 hours
YouTube caps custom thumbnails per channel per day, and the error says to retry in 24 hours. Queue Sume thumbnails and stop the loop on that message.
Written by Sume