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.

4 min readSume
All posts

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

Cloudflare Stream docs, read 2026-10-02
ItemWhat the page says
Basic POST uploadMaximum 200 MB
Above 200 MBtus is required
tus chunk size150 * 1024 * 1024 bytes in the example
tus headerUpload-Length must be sent when the endpoint is created
Duration capmaxDurationSeconds is set when you create the upload URL
ConsequenceYour 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

All Developers posts

Written by Sume