TikTok upload URL 403 expired and 416 Content-Range mismatch
A chunked TikTok upload returns 206 per chunk and 201 at the end. 403 means the upload_url expired after one hour; 416 means Content-Range does not match.

In TikTok's chunked file upload, each chunk you PUT returns 206 until the last one, which returns 201. A 403 says the upload_url has expired, and a 416 says your Content-Range does not match what TikTok expects. Per TikTok's pages, the upload URL is valid for one hour after it is issued and chunks must go up sequentially.
What do the response codes mean?
The Media Transfer Guide lists the four codes below. Chunks are 5 MB to 64 MB (the final chunk may be up to 128 MB), with 1 to 1000 chunks, and videos under 5 MB must be sent whole.
| Code | Meaning | Fix |
|---|---|---|
| 201 | Upload complete | Nothing; move on to status |
| 206 | Chunk received, more pending | Send the next chunk |
| 403 | The upload_url has expired | Initialize again for a new URL |
| 416 | Content-Range mismatch | Recompute byte ranges from the file size |
How do you avoid a 416?
Compute every range from the exact byte size you declared at init. The Content-Range header for a chunk is bytes start-end/total, with end inclusive, so the last byte of a file of N bytes is N minus 1. Declare the same video_size, chunk_size and total_chunk_count you actually send, and never resend a chunk out of order.
The generator below prints the ranges for a file so you can compare them against what your uploader sends.
def ranges(size: int, chunk: int):
start = 0
while start < size:
end = min(start + chunk, size) - 1
yield f"bytes {start}-{end}/{size}"
start = end + 1
for r in ranges(size=150_000_000, chunk=64_000_000):
print(r)How do you avoid a 403?
Do the work that takes time before you initialize. Probe, trim and conform the clip first, then request the upload URL and push the chunks straight away. With Sume, a finished Video trim job hands back a durable URL, so the file is ready to download before the TikTok clock starts.
If the hour passes, the table above says the fix is a fresh initialize call.
Where does Sume stop?
Sume does not upload to TikTok. If you would rather skip chunking, TikTok's PULL_FROM_URL path avoids it, at the cost of a verified domain; see the URL ownership error.
Sources
Related posts
More in Developers
- Timeline 1.0 warnings after stitching shots: which need a fix
Timeline 1.0 returns soft warnings, not failures. A list of the codes you will see on a stitched AI film, what each means, and which ones are worth acting on.
- Trigger.dev version skew protection and in-flight Sume jobs
Trigger.dev pins each run to the deployment from the same commit. What that means for tasks that created a Sume job before a deploy, and what to store.
- TTS boundary_lead_ms: why sentence slices end 70 ms after a word
Sume TTS sentence segments cut boundary_lead_ms after a sentence's last word: 70 ms by default, 0 to 500. The next segment absorbs the pause, and no gap opens.
- tts_duration_exceeded: split long scripts for Sume TTS
Sume TTS fails with tts_duration_exceeded when audio would pass 1,200 seconds, and takes at most 20,000 characters. Split rules and how to join the parts.
Written by Sume