Quickstart
From nothing to a playing video in under ten minutes. Everything below is
plain curl; the executable version of this transcript lives at
docs-src/quickstart.sh and runs in CI against every commit — if this page
drifts from reality, the build goes red.
0. What you need
An API key for your domain — it looks like ek_… and your operator issues
it to you (domains are created on the admin plane; there is no self-serve
signup). Everything below assumes:
BASE=https://energixer.example
API_KEY=ek_…
The key appears once, when it is issued. Store it where your database credentials live: it is your tenant, so it stays server-side and never ships in a client app — see upload tokens for how devices upload without it.
1. Initiate an upload (your backend)
curl -s -X POST "$BASE/v1/videos" \
-H "Authorization: Bearer $API_KEY" \
-H 'content-type: application/json' \
-d '{"content_type": "video/mp4", "file_size": 1048576, "uploader_ref": "user-42"}'
# → {"video_id": "...", "tus_upload_url": "/v1/videos/upload/<id>", "upload_token": "eyJ..."}
file_size is byte-exact — initiate after the file is finalized. Hand
tus_upload_url + upload_token to the device.
2. Upload from the device (tus)
With curl (single chunk; real clients use 2 MB chunks and resume):
# create the tus upload
curl -s -X POST "$BASE/v1/videos/upload" \
-H "Authorization: Bearer $UPLOAD_TOKEN" \
-H 'Tus-Resumable: 1.0.0' \
-H "Upload-Length: $SIZE" \
-H "Upload-Metadata: video_id $(printf %s "$VIDEO_ID" | base64)"
# send the bytes
curl -s -X PATCH "$BASE$TUS_URL" \
-H "Authorization: Bearer $UPLOAD_TOKEN" \
-H 'Tus-Resumable: 1.0.0' -H 'Upload-Offset: 0' \
-H 'content-type: application/offset+octet-stream' \
--data-binary @video.mp4
In a browser, use [tus-js-client] with chunkSize: 2 * 1024 * 1024, retry
delays [0, 1000, 3000, 5000], metadata: {video_id}, and set the
Authorization header in onBeforeRequest. Interrupted? HEAD the upload
URL for the current offset and continue — that is the whole point of tus.
3. Poll until ready
curl -s "$BASE/v1/videos/$VIDEO_ID" -H "Authorization: Bearer $API_KEY"
# state: pending_upload → queued → locked → encoding → available
Poll every ~10 s, or skip polling entirely with webhooks.
4. Play
When state is available, outputs.hls_url is a public URL:
<video id="v" controls></video>
<script src="/path/to/hls.js"></script>
<script>
// Videos are private by default, so the player needs a playback key you
// minted on your server: POST /v1/playback-keys {"viewer_ref": "..."}.
const hls = new Hls({
xhrSetup: (xhr) =>
xhr.setRequestHeader('Authorization', 'Bearer ' + playbackKey),
});
hls.loadSource('https://energixer.example' + outputs.hls_url);
hls.attachMedia(document.getElementById('v'));
</script>
Videos are private by default: delivery answers 401 playback_key_required without a key, and the key goes in the Authorization
header — which means <img> posters, <track> captions and iOS Safari's
native HLS need extra handling. Mark a video public instead and you get the
old behaviour: no auth, CDN-friendly, immutable cache headers on segments.
Both paths, and the browser caveats, are in the
playback guide.