Gate a Format run on TikTok limits: artifact size, length, pixels
Check a Sume Format run's artifacts[] for size, duration and dimensions against TikTok's non-Spark limits before upload. A Python gate under 30 lines.

Every terminal Format run receipt carries artifacts[] with size_bytes, duration_ms, width and height for each file. That is enough to test a finished video against TikTok's non-Spark limits before anyone uploads it: at most 500 MB, up to 10 minutes, and at least 540x960 for 9:16. The check below is 28 lines of Python and runs as written.
Where the numbers come from
The run receipt documents artifacts[] as each durable file the run generated, shaped { id, type, url, content_type, size_bytes, width, height, duration_ms, checksum_sha256 }, empty until the run is terminal. The structured-output page types size_bytes and duration_ms as integer or null, and notes that duration_ms is checked against the file, so a value you read back is the artifact's, or it is null.
TikTok's page (read 2026-10-08) lists the limits the script uses.
| Check | Limit | Receipt field |
|---|---|---|
| File size | 500 MB or less | size_bytes |
| Duration | up to 10 minutes | duration_ms |
| 9:16 pixels | at least 540x960 | width, height |
The check
The script treats null as a failure, not a pass, because a missing value means the number was not measured. It also refuses a run that is not completed, because artifacts[] is only complete once the run is terminal.
MAX_BYTES = 500 * 1000 * 1000 # TikTok: 500 MB or less
MAX_MS = 10 * 60 * 1000 # TikTok: up to 10 minutes
MIN_W, MIN_H = 540, 960 # TikTok 9:16 minimum
def problems(receipt):
if receipt.get("status") != "completed":
return ["run status is %s" % receipt.get("status")]
found = [a for a in receipt.get("artifacts", []) if a.get("type") == "video"]
if not found:
return ["no video artifact"]
out = []
for a in found:
need = ("size_bytes", "duration_ms", "width", "height")
missing = [k for k in need if a.get(k) is None]
if missing:
out.append("%s: %s is null" % (a["id"], ", ".join(missing)))
elif a["size_bytes"] > MAX_BYTES:
out.append("%s: over 500 MB" % a["id"])
elif a["duration_ms"] > MAX_MS:
out.append("%s: over 10 minutes" % a["id"])
elif a["width"] < MIN_W or a["height"] < MIN_H:
out.append("%s: below 540x960" % a["id"])
return out
sample = {"status": "completed", "artifacts": [{"id": "artf_1", "type": "video",
"size_bytes": 4210233, "duration_ms": 12000, "width": 1080, "height": 1920}]}
print(problems(sample) or "ok")What it does not check
It does not read the bitrate (TikTok wants at least 516 kbps), the container or the codecs; none of those is a field on the artifact. Compute a rough bitrate yourself as size_bytes x 8 / duration seconds: the sample is 4,210,233 x 8 / 12 = about 2.81 Mbps. Anything near the floor deserves a probe with video inspect.
It also assumes a 9:16 deliverable. For 16:9 the minimum is 960x540, and for 1:1 it is 640x640. Add the ratio to your Format's output_schema so the check knows which minimum applies.
Wiring it in
Run it in the webhook handler or the poll loop after status reaches completed. If it returns problems, treat the run as a failed delivery in your own system and retry with a changed instruction, rather than uploading anyway.
Mind the 500 MB reading: TikTok writes MB, and the script uses 500 x 1,000,000 bytes, which is the stricter reading.
Why gate before upload
A rejected upload costs you a round trip and, for a team, a manual step. Every limit this script tests is a number TikTok's page states for non-Spark ads: 500 MB, 10 minutes and the per-ratio minimum sizes. The script reads the same numbers off the Format run receipt, so the answer is available the moment the run completes and before any bytes leave your account.
Keep the limits in one table at the top of your own copy so a change on TikTok's page is a one-line edit. Re-read the page on the date you ship, because the sample reflects the page as read on 2026-10-08.
A pass here means only that three numbers are inside TikTok's stated limits. It is not a check of content rules, audio, captions or bitrate, and it does not replace the checks TikTok runs on upload.
Sources
Related posts
More in Developers
- gemini-nano-banana-2.1 or google/nano-banana-2.1: which id Sume takes
Google's pricing page names the model gemini-nano-banana-2.1. Sume's image API takes google/nano-banana-2.1 and runs the old nano-banana-2 as 2.1.
- Gemini Omni video references: 3 clips of 3 s each, $1.31 all in
Sume's Omni route takes up to 3 reference clips of 3 s each. Trimming three source clips at $0.02 each plus a 10 s 720p render at $1.25 is $1.31.
- Gemini Omni reference-to-video: 3-second clips and IMAGE_REF tags
Omni on Sume takes up to 10 reference images and 3 reference videos of at most 3 s each, addressed as <IMAGE_REF_0> and <VIDEO_REF_0> in the prompt.
- Gemini Omni video edit on Sume: how an unknown clip length is priced
Omni edit takes a video_url and a prompt; output follows the source length. Sume reserves 8 s ($1.00 at 720p) when unknown and accepts a hint up to 30 s.
Written by Sume