Upload a video
Three steps: ask where to put the file, send the bytes there, then tell us it has landed. The file never passes through the API.
Step 1 — Get an upload URL
/api/public/v1/videos/upload-url/Body
| Key | Type | Sample | Description |
|---|---|---|---|
| file_namerequired | string | lecture-01.mp4 | Used to build the storage key and to keep the extension. Max 255 characters. |
curl -X POST https://api.streamos.in/api/public/v1/videos/upload-url/ \
-H 'Authorization: Bearer sk_live_…' \
-H 'Content-Type: application/json' \
-d '{"file_name": "lecture-01.mp4"}'Response — data
| Key | Type | Sample | Description |
|---|---|---|---|
| upload_urloptional | string | https://…?q-sign-algorithm=sha1&… | Pre-signed PUT target. Send the file here. |
| public_urloptional | string | https://…/streamos/1786997053554-…-lecture-01.mp4 | Where the file will live. Pass this as source_url in step 3. |
| storage_keyoptional | string | streamos/1786997053554-…-lecture-01.mp4 | The object key, if you keep your own records. |
Step 2 — Send the file
<upload_url>A plain PUT of the file body, straight to storage. This request does not go to the StreamOS API and takes no API key — the signature in the URL is the authorisation.
curl -X PUT "<upload_url>" \ -H 'Content-Type: video/mp4' \ --data-binary @lecture-01.mp4
Expect 200. Set Content-Type to match the file so the stored object is typed correctly.
Step 3 — Register the video
/api/public/v1/videos/This creates the asset and queues transcoding. It returns immediately — the video is not playable yet.
Body
| Key | Type | Sample | Description |
|---|---|---|---|
| titlerequired | string | Lecture 1 — Introduction | Shown in the library. Max 200 characters. |
| source_urlrequired | string (url) | https://…/lecture-01.mp4 | The public_url from step 1, or any publicly reachable URL you already host. |
| folder_public_idoptional | string | Kd8xQb2rLmT | Place it in a folder. Omitted means the root of the library. |
| original_filenameoptional | string | lecture-01.mp4 | Kept for your reference and shown on the asset page. |
| content_typeoptional | string | video/mp4 | MIME type of the source file. |
| size_bytesoptional | integer | 5913969 | Source size, used for storage reporting. |
| drm_enabledoptional | boolean | false | This video’s own DRM choice, overriding your organization’s default. Omit it to inherit that default. Sending true when your plan does not include DRM is refused with DRM_NOT_AVAILABLE (409), not silently ignored. |
| customTagsoptional | object | {"course": "Physics 101"} | Your own key/value pairs, up to 20. Stored on the asset and forwarded to the transcoder. |
curl -X POST https://api.streamos.in/api/public/v1/videos/ \
-H 'Authorization: Bearer sk_live_…' \
-H 'Content-Type: application/json' \
-d '{
"title": "Lecture 1 — Introduction",
"source_url": "https://…/lecture-01.mp4",
"customTags": {
"course": "Physics 101",
"batch": "JEE-2026",
"internal-id": "crs_88213"
}
}'drm_enabledis left out above on purpose — the common case is inheriting your organization’s own default, which means not sending the field at all. Add "drm_enabled": false (or true) only when this one upload needs to differ from that default; see Overriding DRM for one upload below.{
"success": true,
"message": "Video added.",
"data": {
"asset": {
"public_id": "oI1j0SfJK3v",
"title": "Lecture 1 — Introduction",
"status": "TRANSCODING",
"transcoder_id": "4233029",
"created_at": "2026-08-17T20:36:23.705Z"
}
}
}Tags
customTags is a flat object of your own key/value pairs — up to 20, keys of 40 characters and values of 200. Use them to tie a video back to a record in your own system: a course, a batch, an internal id.
They are stored on the asset and read back as tags on GET /assets/<public_id>/. Omit the field entirely if you have none — an empty object is not required.
"customTags": {
"course": "Physics 101",
"batch": "JEE-2026",
"internal-id": "crs_88213"
}VALIDATION_ERROR.Encoding settings
drm, resolutions and generate_subtitlesare no longer part of this request. They are settings on your organization now, configured once by the StreamOS team, and every upload and stream recording uses them — so you never restate them, and they can be changed for you without re-uploading anything.
An organization with no encoding settings cannot upload or start a stream. The request is refused with TRANSCODE_NOT_CONFIGURED (409) and the message “This organization has no transcoding settings yet. Ask the StreamOS team to configure them.” — nothing is created. Talk to us and it is a one-minute fix on our side.
Overriding DRM for one upload
drm_enabledis the one exception to the section above — not a way to configure encoding generally, only a per-upload override of your organization’s own DRM default. Omit it and this upload gets whatever your organization is set to. Send true or falseand this one video uses that instead, whichever way your organization’s own default runs.
The one direction that is refused: sending drm_enabled: truewhen your organization’s plan does not include DRM at all. That comes back DRM_NOT_AVAILABLE (409) — “Your plan does not include DRM. Ask the StreamOS team to enable it for your organization.” — refused before anything is created, not created unprotected and left for you to notice. Turning it off for an organization that defaults to DRM is always allowed.
curl -X POST https://api.streamos.in/api/public/v1/videos/ \
-H 'Authorization: Bearer sk_live_…' \
-H 'Content-Type: application/json' \
-d '{
"title": "Lecture 1 — Introduction",
"source_url": "https://…/lecture-01.mp4",
"drm_enabled": false
}'Knowing when it is ready
Transcoding takes minutes, not seconds. The asset moves through TRANSCODING to either COMPLETED or ERROR.
Poll
curl https://api.streamos.in/api/public/v1/assets/oI1j0SfJK3v/ \ -H 'Authorization: Bearer sk_live_…'
Or subscribe
Register a webhook for VIDEO_TRANSCODING_COMPLETED and we post the finished manifest and every rendition to you. No polling loop to write, no interval to tune.