Upload a video
A job starts from a source: a file you upload, or one at a URL that the job copies. Either way it becomes an upload in your organization, which later jobs can reuse for 7 days without sending it again. After that it is deleted.
Start an upload
Section titled “Start an upload”POST /v1/uploads{ "file_name": "video.mov", "content_type": "video/quicktime", "size": 2176298566 }| Field | |
|---|---|
file_name |
Shown in the dashboard. |
content_type |
Sent again as Content-Type in each PUT. Default application/octet-stream. |
size |
Bytes. Up to your plan’s maximum file size (402 quota_exceeded above it). |
progressive |
true for a progressive upload. |
The answer’s mode says how to send the file. Upload URLs last one hour.
Up to 100 MB: one request
Section titled “Up to 100 MB: one request”mode: "single". PUT the whole file to url, with the headers given, then complete it:
curl -X PUT "$URL" -H "Content-Type: video/quicktime" --data-binary @video.movcurl -X POST https://api.iomza.com/v1/uploads/$ID/complete -H "Authorization: Bearer $IOMZA_API_KEY"Over 100 MB: in parts
Section titled “Over 100 MB: in parts”mode: "multipart". The file is cut into parts of part_size bytes (the last one shorter), and parts has a URL for each. PUT them, several at a time if you like, and keep the ETag header of each response. Then complete the upload with every part:
POST /v1/uploads/{id}/complete{ "parts": [{ "part_number": 1, "etag": "\"9b2cf535f27731c974343645a3985328\"" }, { "part_number": 2, "etag": "\"…\"" }] }If URLs expire before you are done, ask for new ones for the parts still missing:
POST /v1/uploads/{id}/parts{ "part_numbers": [7, 8] }A part that fails can be sent again to the same URL. Completing twice is harmless: the second call returns the upload as it is.
Progressive uploads: process while it uploads
Section titled “Progressive uploads: process while it uploads”With "progressive": true, each part is its own object, readable as soon as it lands, and there is no complete. Create the job as soon as the upload starts (input.upload_id): it is transcoded while the file arrives, so the wait after the last byte is a fraction of a normal job’s.
PUTeach part with theheadersgiven.- Send the first and last parts first (containers keep their header and index there), then the rest in order.
- The job’s
uploadtask shows what has arrived (bytes/total_bytes). - If nothing arrives for 30 minutes, the job fails with
upload_incomplete. - If the job fails before you finish (the file can’t be read, for example), stop uploading: the rest won’t be used. Watch for the
job.failedwebhook or check the job. - Until it is complete, the upload feeds only its own job; after that, other jobs can use it like any other.
This is how the dashboard uploads.
Or give a URL
Section titled “Or give a URL”Instead of uploading, create the job with "input": { "url": "https://…" }. The job copies the video first; its import task shows the progress. When the server accepts byte ranges (S3, Google Cloud Storage, Azure, CDNs and almost any web server), the copy is fast and the video is transcoded while it downloads; otherwise it is copied whole, then processed.
The URL must:
- be https, public or presigned (a presigned URL must stay valid until the copy ends);
- reach the file in at most 5 redirects; private and internal addresses are refused;
- point to a file no larger than your plan’s maximum.
For S3 buckets whose name has dots, use the path-style URL (https://s3.<region>.amazonaws.com/<bucket>/<key>): the virtual-hosted one doesn’t match the bucket’s certificate.
The copy becomes an upload (mode: "url") that later jobs can use by upload_id for 7 days. If it fails, the job ends with source_import_failed and the reason (HTTP 404, timeout, too large…).
Look inside before creating a job
Section titled “Look inside before creating a job”To choose audio or subtitle tracks, or to refuse a file early, read what a source contains. Only its header is read: about a second, even for 20 GB.
GET /v1/uploads/{id}/tracksPOST /v1/probe { "url": "https://…" }{ "object": "source_tracks", "duration": 7216.8, "bitrate": 21129954, "video": { "codec": "hevc", "width": 3840, "height": 1600, "frame_rate": 24, "interlaced": false, "hdr": "pq", "unsupported": null }, "audio_tracks": [ { "index": 0, "language": "spa", "title": null, "codec": "eac3", "channels": 8, "default": true, "atmos": false, "bitrate": 768000 }, { "index": 1, "language": "eng", "title": null, "codec": "eac3", "channels": 6, "default": false, "atmos": true, "bitrate": 640000 } ], "subtitle_tracks": [ { "index": 0, "language": "spa", "title": null, "codec": "subrip", "default": true, "forced": true, "convertible": true } ]}video.hdr:pq(HDR10 and Dolby Vision) orhlg. HDR sources are tone mapped to SDR, which every screen shows correctly.video.unsupported: a video we can’t process, so you can refuse it before anyone waits for a job. Today the one case isdolby_vision_no_compatible_layer: Dolby Vision profiles 5 and 20, whose colours can’t be reproduced faithfully. Export those as HDR10, HLG, SDR or Dolby Vision profile 8.subtitle_tracks[].convertible: text subtitles (SRT, ASS, WebVTT, mov_text…) become WebVTT; image ones (PGS, VobSub) can’t.
Which files work
Section titled “Which files work”Any common container and codec: MP4, MOV, MKV, WebM, AVI, MPEG-TS, MXF…, with H.264, HEVC, ProRes, VP9, AV1, MPEG-2 and others. Interlaced video is deinterlaced, variable frame rates are made steady, rotation from phones is applied, and anamorphic pixels are corrected: the job’s adjustments say what was done.