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.

Already hosting the file on a public URL? Skip to step 3 and post that URL directly.

Step 1 — Get an upload URL

POST/api/public/v1/videos/upload-url/

Body

KeyTypeSampleDescription
file_namerequiredstringlecture-01.mp4Used to build the storage key and to keep the extension. Max 255 characters.
curl
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

KeyTypeSampleDescription
upload_urloptionalstringhttps://…?q-sign-algorithm=sha1&…Pre-signed PUT target. Send the file here.
public_urloptionalstringhttps://…/streamos/1786997053554-…-lecture-01.mp4Where the file will live. Pass this as source_url in step 3.
storage_keyoptionalstringstreamos/1786997053554-…-lecture-01.mp4The object key, if you keep your own records.
The signature is short-lived — around 15 minutes. Request it when you are ready to send, not when your job starts.

Step 2 — Send the file

PUT<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
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.

This is a single PUT, so the ceiling is 5 GB and there is no resume — a dropped connection means starting the file again. Multipart uploads are planned.

Step 3 — Register the video

POST/api/public/v1/videos/

This creates the asset and queues transcoding. It returns immediately — the video is not playable yet.

Body

KeyTypeSampleDescription
titlerequiredstringLecture 1 — IntroductionShown in the library. Max 200 characters.
source_urlrequiredstring (url)https://…/lecture-01.mp4The public_url from step 1, or any publicly reachable URL you already host.
folder_public_idoptionalstringKd8xQb2rLmTPlace it in a folder. Omitted means the root of the library.
original_filenameoptionalstringlecture-01.mp4Kept for your reference and shown on the asset page.
content_typeoptionalstringvideo/mp4MIME type of the source file.
size_bytesoptionalinteger5913969Source size, used for storage reporting.
drm_enabledoptionalbooleanfalseThis 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.
customTagsoptionalobject{"course": "Physics 101"}Your own key/value pairs, up to 20. Stored on the asset and forwarded to the transcoder.
curl
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.
201 Created
{
  "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.

json
"customTags": {
  "course": "Physics 101",
  "batch": "JEE-2026",
  "internal-id": "crs_88213"
}
Keys accept letters, digits, spaces, dots, dashes and underscores, and must start with a letter or digit. Anything else is rejected with 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.

This is a breaking change.If your integration still sends those three fields they are ignored, not rejected — nothing errors, but nothing they say is honoured either. Remove them.

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 — this upload only, regardless of the org default
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
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.