LinkedIn Videos API: 4 MB parts, ETags and finalizeUpload
LinkedIn video upload is initializeUpload, PUT each 4 MB part, collect the ETags, then finalizeUpload. A part splitter and the order rules that break uploads.

LinkedIn's Videos API uploads in four steps: call initializeUpload with the owner and fileSizeBytes, split the file into 4 MB parts, PUT each part to its upload URL and keep the ETag from every response, then call finalizeUpload with the video URN, the upload token and the ETags in the same order as the parts. The finalize step is order-sensitive, so keep the ETags in a list indexed by part. The splitter below gives you the byte ranges LinkedIn expects.
Rules on the page
| Item | What the page says |
|---|---|
| Initialize | POST /rest/videos?action=initializeUpload; fileSizeBytes decides the part count |
| Part size | First range 0-4194303, next 4194304-8388607, and so on |
| Part upload | HTTP PUT to each uploadUrl; save the ETag response header |
| Finalize | POST /rest/videos?action=finalizeUpload with video, uploadToken and uploadedPartIds |
| Order | uploadedPartIds must match the order in uploadInstructions |
| URL expiry | Typically 30 days after initialize; an expired URL gives 401 |
| Headers | Linkedin-Version and X-Restli-Protocol-Version: 2.0.0 on every call |
A splitter that matches the ranges
Read the ranges from the uploadInstructions response rather than computing them, but the same arithmetic is useful for tests: every part is 4,194,304 bytes except the last.
PART = 4 * 1024 * 1024
def ranges(total):
if total <= 0:
raise ValueError("empty file")
out, first = [], 0
while first < total:
last = min(first + PART, total) - 1
out.append((first, last))
first = last + 1
return out
for a, b in ranges(10 * 1024 * 1024)[:3]:
print(a, b)
Getting `fileSizeBytes`
The size must be exact. If the file came out of Sume, take the byte length from your own download of the file. For clips that need shortening before upload, video trim cuts a range for $0.02 per job.
Upload loop shape
Run the PUTs from the uploadInstructions array in order, send each byte range exactly as named (firstByte through lastByte, inclusive), and append the ETag header value to a list before moving on. If a part fails, retry that part, not the whole file. When every part has an ETag, finalize once. Because the upload URLs usually last 30 days, a resumed job can often continue, but an expired URL returns 401 and means you must initialize again. Poll the video by its URN until status is AVAILABLE; the page lists PROCESSING, PROCESSING_FAILED, AVAILABLE and WAITING_UPLOAD.
Limits
The page also lists a general size range of 75 KB to 500 MB, a length of 3 seconds to 30 minutes, and MP4 only, and elsewhere a 5 GB cap on fileSizeBytes; those two numbers disagree, which is covered in 500 MB versus 5 GB. Send MP4 and stay under 500 MB. Sume cannot see or store the ETags; they belong to your uploader.
Sources
Related posts
More in Developers
- MAI-Image-2.6 allows 6 requests a minute at tier 1; Sume queues
Foundry rates MAI-Image-2.6 at 6 RPM on tier 1 and 429s past it. Sume accepts valid image jobs as queued until a plan slot opens. Compare the two behaviours.
- MAI-Image-2.6 returns base64 PNG; Sume returns a hosted image URL
Foundry returns MAI-Image-2.6 as b64_json PNG only. Sume returns a signed media URL in data[].url, in png, jpeg or webp per model. How to handle each in code.
- Make an AI avatar video from the terminal with the Sume CLI
sume avatars create and sume avatar-videos create submit Avatar 1.0 jobs from a shell. Flags, the --confirm-paid guard, and how to recover the job.
- Migrate Veo 3.1 API calls to Gemini Omni Flash 1.1: parameter map
Veo 3.1 previews shut down October 22, 2026. A parameter-by-parameter map from the Veo guide to Gemini Omni Flash 1.1 requests on Sume, with a working curl.
Written by Sume