Playback

One rule: nobody builds video URLs by hand. You ask GET /v1/videos/{id}/playback and give hls_url to your player. Our own dashboard works the same way.

When to call it

Call /playback when the player opens, never earlier. On Strict, a link has ten minutes to start; a link prepared in advance may be dead by the time the viewer presses play.

You call from Use it for
Your web page or app, no key Public videos on Open or Protected
Your backend, with the secret key Private videos and Strict workspaces

Optional query parameters:

  • viewer_id: an opaque id of your own (your user id, a course seat). It travels inside the link and lands in our logs, so a leaked link can be traced back to whoever received it.

What you get

Field What it is
hls_url Master playlist for the player. null while rendering or on original.
original_url The untouched original. Same expiry and security as hls_url.
poster_url Still image for the player poster, or null.
thumbnails_url WebVTT sprite for seek previews, or null.
embed_url Our hosted player (iframe). On Strict it only works with this exact URL.
expires_at When the link stops working (ISO 8601), or null on Open.
security The level that applied: open, protected, strict or maximum.
delivery hls or original. See below.
duration_sec Video length in seconds.

The token lives in the path (/hls/t/{token}/…) because AVPlayer, Chromecast and native apps cannot send headers. Every playlist and segment under that path is already signed; you never sign anything yourself.

Original or HLS

Every video keeps its original file exactly as you uploaded or imported it. We never transcode the original. You choose a delivery when you upload or import:

  • hls (default): XonaPlay renders adaptive HLS from the original. Play hls_url.
  • original: no transcoding at all. The original is what you play and download. Play original_url.

While HLS is still rendering, hls_url is null and original_url already works. original_url supports byte-range requests. With original, hls_url is always null.

XonaPlay guarantees hls_url on every device. Which players can play original_url depends on its format: mp4 with H.264 plays almost everywhere, mkv and HEVC may not. That choice is yours.

Without HLS, poster_url and thumbnails_url are null, unless the owner set a custom poster.

Videos stored on your own server (BYO) return original_url: null.

To move a video uploaded with original to HLS, send PATCH /v1/videos/{id} with "delivery": "hls". It works one way only, and original_url keeps working in the meantime.

Expiry

Level The link works for
Open Forever
Protected Video length + 1 hour (2 to 24 hours)
Strict 10 minutes to start, then video length + 30 minutes (1 to 24 hours)

A paused tab can outlive its link. That is expected: the player recovers.

Reload rule

Every recipe in these docs follows the same four steps:

  1. On any fatal player error (a 403 on a segment, a network error, a decode error), call /playback again — at most once per minute — and resume at the same second.
  2. If the response has hls_url, load it and seek back.
  3. If the response is an error, show its message. Offer a Reload button only when the code is retryable (see the table below).
  4. If the player fails again within 60 seconds, do not call anything: show Reload, which goes back to step 1.

The decision comes from the JSON of /playback, which every HTTP client can read. Do not rely on HTTP headers from the video host: AVPlayer cannot read them.

Errors

Errors use the same envelope as the rest of the API:

json
{ "status": "error", "code": "KEY_REQUIRED", "message": "This video needs a secret key." }
code HTTP Retry?
NOT_FOUND 404 No
KEY_REQUIRED 401 No. Call it from your backend with the key.
INVALID_KEY 401 No
DOMAIN_NOT_ALLOWED 403 No
WORKSPACE_UNAVAILABLE 403 No
ACCESS_DENIED 403 No
VIDEO_NOT_READY 409 Yes, later
LIVE_NOT_ACTIVE 409 Yes, later
RATE_LIMITED 429 Yes, after Retry-After
5xx or a response without JSON — Yes

The video host (not /playback) answers a dead link with a plain 401/403/503 and an X-Xona-Error header — TOKEN_EXPIRED, TOKEN_REVOKED, NETWORK_CHANGED, MAXIMUM_UNAVAILABLE… It is there for debugging; your player just follows the reload rule, and MAXIMUM_UNAVAILABLE (503) is retryable like the rest.

Live

Live streams use the same contract:

bash
curl https://api.xonaplay.com/v1/live/{STREAM_ID}/playback

LIVE_NOT_ACTIVE means the stream is not on air yet. Retry later or show a countdown.