After this lesson you'll be able to upload a video via a direct creator upload URL, embed the resulting player, stand up a basic live input, and budget correctly for both storage and delivery minutes.
Stream is Cloudflare's managed video platform: you give it a video file or a live encoder feed, and it handles encoding, storage, adaptive-bitrate delivery, and a drop-in player — without you running transcoding servers or a video CDN yourself. It covers both on-demand video (upload once, play many times) and live video (ingest an RTMPS/SRT feed, distribute it to viewers, optionally record it for later on-demand playback) through one API and one billing model.
The core object is a video, identified by a uid. You get a video into Stream one of two ways:
Once a file lands, Stream transcodes it to H.264 across multiple renditions (up to 1080p) for adaptive-bitrate streaming — the player automatically switches resolution as the viewer's bandwidth changes, the same idea as HLS/DASH elsewhere, but you don't configure the ladder yourself. When processing finishes, the video is playable via Stream's hosted player (an iframe you embed with the uid) or via direct HLS/DASH manifest URLs if you're using your own player.
Live video uses a separate object, a live input, created via API or dashboard. Creating one returns ingest credentials for two supported protocols:
Both expect closed GOPs, a stable bitrate (typically under 12 Mbps), and keyframes every 2–8 seconds — standard live-encoder settings, not Stream-specific tuning. Viewers watch the live input the same way they watch on-demand video: through the hosted player or a manifest URL, pointed at the live input's playback ID instead of a video uid. If you set the input's recording mode to automatic, the broadcast is also saved as an on-demand video the moment it ends — billed as separate stored minutes, not free just because it started as live.
Step 1 — your backend requests a one-time upload URL (never expose your API token to the browser):
curl https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/direct_upload \
--header 'Authorization: Bearer <API_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{"maxDurationSeconds": 3600}'
Response — save the uid, hand the uploadURL to the client:
{
"result": {
"uploadURL": "https://upload.videodelivery.net/f65014bc6ff5419ea86e7972a047ba22",
"uid": "f65014bc6ff5419ea86e7972a047ba22"
},
"success": true
}
Step 2 — the client uploads the file straight to that URL (files under 200MB can use a plain POST; larger or unreliable-connection uploads should use the resumable tus protocol instead):
curl --request POST \
--form file=@my_video.mp4 \
https://upload.videodelivery.net/f65014bc6ff5419ea86e7972a047ba22
Step 3 — once Stream finishes processing (poll the video-details endpoint with the uid, or use a webhook), embed the player:
<iframe
src="https://customer-<code>.cloudflarestream.com/f65014bc6ff5419ea86e7972a047ba22/iframe"
style="border:none; width:100%; aspect-ratio:16/9"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowfullscreen>
</iframe>
That's the entire "user-generated video" pipeline: your backend never handles the raw file, and there's no transcoding code anywhere in your app.
Stream bills on two independent dimensions:
| Dimension | Rate |
|---|---|
| Minutes stored | $5 per 1,000 minutes stored |
| Minutes delivered | $1 per 1,000 minutes delivered (playback, MP4 downloads, simulcasting) |
Storage cost is based on video duration, not file size or resolution. Delivery is post-paid and usage-based — bandwidth itself has no separate line item; it's folded into the per-minute-delivered price. For live: ingesting and encoding the incoming feed is free — you're only billed once a live stream is delivered to a viewer or gets recorded to storage. A live stream nobody watches costs nothing beyond whatever recording gets stored. These numbers can change — confirm current rates on the Stream pricing page before quoting them to a client or building a cost model.
Cloudflare Stream — Direct creator uploads is the canonical reference for the upload flow in this lesson; pair it with the Stream pricing page for current rates, since pricing is subject to change.
Live inputs accept RTMPS (URL + stream key, the classic OBS-style setup) and SRT (caller mode — the encoder initiates the connection to Cloudflare). For direct creator uploads, the backend requests a one-time upload URL from Stream's API and hands only that URL to the client — the API token itself is never exposed to the browser or app.