Cloudflare Stream 200 MB upload limit: when you must use tus
Cloudflare Stream accepts basic uploads up to 200 MB and requires tus above that. Read the size from a Sume probe and route the file before you upload.

Cloudflare Stream takes a basic multipart POST only for files under 200 MB. Anything bigger has to go through the tus protocol, and Cloudflare recommends tus on shaky connections even below 200 MB. If your pipeline produces clips of varying size, read the file size first and pick the path in code. Sume's unbilled video inspect gives you that size without downloading the file.
Facts from the Cloudflare page
| Item | What the page says |
|---|---|
| Basic POST upload | Maximum 200 MB |
| Above 200 MB | tus is required |
| tus chunk size | 150 * 1024 * 1024 bytes in the example |
| tus header | Upload-Length must be sent when the endpoint is created |
| Duration cap | maxDurationSeconds is set when you create the upload URL |
| Consequence | Your backend must learn the file size before it asks for a tus URL |
Read the size without a download
POST /v1/video-inspect on a media.sume.com clip returns a probe object that includes size_bytes, duration_seconds, width, height and fps. Probe and stills are unbilled; passing frames: false skips the stills. The clip must already be in your workspace, so import it first (see media inputs).
import os, requests
from time import sleep
API = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def size_bytes(video_url):
r = requests.post(API + "/v1/video-inspect", timeout=60, json={
"video_url": video_url, "frames": False},
headers={**H, "Idempotency-Key": "size-" + video_url[-24:]})
r.raise_for_status()
data = r.json()["data"]
vi = data["video_inspect"]
while vi["resource_status"] == "processing" or vi["status"] == "queued":
sleep(2)
got = requests.get(f"{API}/v1/video-inspect/{data['request_id']}",
headers=H, timeout=60).json()["data"]
vi = got["video_inspect"]
return (vi.get("probe") or {}).get("size_bytes")
def route(video_url):
size = size_bytes(video_url)
if size is None:
return "tus"
return "basic" if size < 200 * 1024 * 1024 else "tus"
If the file is too big
Sume has no bitrate, codec or CRF field: the server rejects them with ffmpeg_fields_rejected. What you can change is length and frame size. Video trim takes start plus end or duration and an output of width/height (256 to 2160) and fps (24, 25, 30 or 60), exact precision only. A smaller frame size usually shrinks the file, but we do not publish a size guarantee, so probe the new MP4 again.
Limits
The sample treats an unknown size as tus, which always works. It does not cover Cloudflare's other limits, such as account storage, and the page does not state a cap for maxDurationSeconds. The loop assumes the data envelope and the resource_status field that the API returns for inspect jobs; handle failed and canceled in real code instead of treating a missing probe as tus.
Sources
Related posts
More in Developers
- Compare AI image models on the same prompt: a 20-line API script
MAI-Image-2.6 and Muse Image both claim No. 2 on Arena. Skip the leaderboard: run one prompt through several Sume image models and compare the URLs and cost.
- Why concurrency_limit differs from the Sume plan table
In Sume generation_limits, concurrency_limit is the effective cap and limit_source says plan or admin_override. Size work from it, not plan_concurrency_limit.
- curl --retry on a POST: retry a Sume submit with one key
curl --retry also retries a POST, and it resends the same headers each time. Put an Idempotency-Key on a Sume submit first, then pick --retry-max-time.
- Decart lucy-latest vs a pinned Lucy model; Sume catalog ids
Decart's lucy-latest alias can move while legacy Lucy Clip costs $0.15 per second against $0.04 for Lucy 2.5. Why pin a model id, and how to do it on Sume.
Written by Sume