Secure playback
Request a one-time password (OTP) from your server for each viewer, then embed the player with it.
For paid or private videos, your server requests a fresh OTP for every signed-in viewer on every page view, and your page embeds the player with it.
How it works
- A signed-in viewer opens a page on your site that shows a video.
- Your server checks that this viewer may watch the video, then calls
POST /playback/otpwith the library's private key. - OctaVideo returns an OTP and an
embed_urlthat already contains it. - Your page renders an iframe with that
embed_url. - The player redeems the OTP, checks the page's domain and the viewer's country against the library's security settings, and starts playback.
An OTP can be used once. Once the player redeems it, the viewer keeps watching, seeking and changing quality in that playback session without a new OTP.
Request an OTP
POST https://api.octavideo.tech/api/v1/playback/otp
AccessKey: ovk_your_private_key
Content-Type: application/jsonProp
Type
A successful request returns 201 Created:
{
"data": {
"otp": "Zx8kQ2mVb7nR4tY1uW9eA3sD6fG0hJ5kL2pO8iU7yT4",
"expires_at": "2026-10-25T09:35:00Z",
"embed_url": "https://player.octavideo.tech/embed/0b8e5d3a-1c2f-4e6a-8b7d-9f0a1b2c3d4e/6f1c2b9e-4a7d-4f3e-9b8a-2d5c7e1f0a34?otp=Zx8kQ2mVb7nR4tY1uW9eA3sD6fG0hJ5kL2pO8iU7yT4"
}
}otpis 43 characters long and works once.expires_atis when the OTP expires if nobody uses it, in UTC.embed_urlis the player URL to load in the iframe. Use it as is.
Always pass user_id
OctaVideo counts unique viewers by user_id. With it, one person counts once across their phone, laptop and tablet. Without it, each browser counts as a separate viewer, so the same student on two devices counts twice.
- Send the viewer's ID from your own system, such as
student-4821. Prefer an internal ID to an email address. - It is up to 128 printable ASCII characters, with no spaces.
- Send the same
user_idfor the same person every time.
Embed the player
Request the OTP on your server right before you render the page, then put embed_url in the iframe. Each example reads the key from the OCTAVIDEO_KEY environment variable and escapes the URL before writing it into HTML:
curl -X POST "https://api.octavideo.tech/api/v1/playback/otp" \
-H "AccessKey: $OCTAVIDEO_KEY" \
-H "Content-Type: application/json" \
-d '{"video_id": "6f1c2b9e-4a7d-4f3e-9b8a-2d5c7e1f0a34", "user_id": "student-4821", "ttl_seconds": 300}'
# Then, in your page:
# <iframe src="EMBED_URL" style="border:0;width:100%;aspect-ratio:16/9"
# allow="encrypted-media; fullscreen" allowfullscreen></iframe>The iframe takes the width of its container and keeps a 16:9 ratio, so your page decides the size.
Best practices
- One OTP per page view. Request a new OTP every time the page renders. Never cache an OTP, share it between viewers or reuse it after a reload.
- Request it right before rendering. The default
ttl_secondsof 300 gives the browser 5 minutes to load the player. Raise it only for pages that stay open a long time before the video loads. - Check access first. OctaVideo trusts your server: request an OTP only after you have checked that the viewer may watch this video.
- Keep the key on the server. The browser only ever sees
embed_url. See Authentication.
Errors
When you request the OTP:
| Status | Code | Meaning |
|---|---|---|
400 | validation_failed | A field is missing or invalid; details.fields names it. |
401 | unauthorized | The AccessKey header is missing or the key is invalid. |
404 | not_found | The video doesn't exist in this library. |
409 | video_not_ready | The video is still processing or failed. Wait until it is Ready. |
When the player loads, it refuses to play and shows an error if:
| Code | Meaning | Fix |
|---|---|---|
unauthorized | The OTP was already used or has expired. | Request a new OTP for each page view. |
domain_not_allowed | The page that embeds the player isn't on the library's allowed domains. | Add the domain in Allowed domains. |
country_blocked | The library blocks playback from the viewer's country. | Review country blocking. |
See Errors for the error format and the full list of codes.