Bản lưu ngày / Snapshot of 2026-10-05 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaThese docs are in beta and change often. Something wrong, unclear or missing? Tell us.
API reference

Request playback

Markdown
On this page

Issues short-lived playback tickets: one episode, or a batch of up to 3 (the episode a viewer starts plus up to two prefetches). Your backend calls this each time a viewer starts an episode and passes the result to the Player SDK on the viewer's device. Lifetime, reuse and batch rules: Canonical API behaviour.

POST /api/delivery/v1/playback

Scope: playback (Developers → Stream films in an app; see Authentication)

Description

Wandura checks, on every request, that your organisation is active and that your streaming licence for the episode's film is active and inside its window. It then returns a play token bound to the viewer's device. Licensed episodes are encrypted and play only through the Player SDK, so you must send the device values union_info and drm_kek. See Playback.

Call it only from your backend. The endpoint sends no CORS headers.

Request parameters

Name In Type Required Description
Authorization header string Yes Bearer <API key> for a key with the playback scope.
Content-Type header string Recommended application/json. The body must be JSON.
Wandura-Version header string No Optional. A dated API version (YYYY-MM-DD) to use for this request instead of the version pinned to your API key. Unknown or retired values answer 400 with error_code unsupported_version.
Wandura-Simulate header string No Optional, test organisations only. Asks Wandura to answer with a simulated error so you can test how your app handles it, for example rate_limited, service_unavailable or not_entitled:outside_window. The simulated response has the same status, body and headers as the real one, plus a Wandura-Simulated header naming the scenario. It issues no ticket and does not count against your rate limits, except partial_batch, whose first item is a real ticket. Simulated responses have their own limit of 300 per minute per organisation. Keys of production organisations ignore this header completely. An unknown value, or a scenario this endpoint does not support, is ignored and the request runs normally. See the Testing guide for the list of scenarios.
episode_id body string (UUID) One of episode_id or film_ref + episode_no The episode's id from the catalog. Takes precedence if you also send film_ref.
film_ref body string With episode_no, if no episode_id The film's id or slug.
episode_no body integer With film_ref, if no episode_id Episode number, starting at 1. Must be a JSON number, not a string.
union_info body string Yes, for encrypted episodes Device ID generated by the Player SDK on the viewer's device. Send together with drm_kek. Rejected with 400 invalid_device_info when, after trimming spaces, it is shorter than 8 characters or is one of (null), null, nil, undefined, nan, <null> or none (any letter case).
drm_kek body string Yes, for encrypted episodes Device public key generated by the Player SDK. Send together with union_info. Same validation as union_info. The matching private key never leaves the device.
intent body string No play when the viewer tapped the episode, prefetch when your app loads it ahead of a tap. Prefetch requests are not counted as plays. A missing or other value is recorded as unknown, never rejected, and counted as a play.
viewer_ref body string No Your own opaque session or device identifier, stored with the ticket for support lookups (up to 256 characters). Do not include personal data. When you send it, asking again for the same episode with the same viewer_ref, intent (play or prefetch) and device values while your previous ticket still has at least 5 minutes left returns that same ticket (same expires_at, not extended) and does not count against your rate limit.
episodes body array No Batch form A — up to 3 episodes in one call (typically the one being watched + the next 2). Cannot be combined with top-level episode_id / film_ref / episode_no or with next. Item 0 takes the top-level intent; later items default to prefetch unless they carry their own.
episodes[].episode_id body string (UUID) No Episode ID. Or send film_ref + episode_no.
episodes[].film_ref body string No Film id or slug, with episode_no.
episodes[].episode_no body integer No Episode number (whole number from 1), with film_ref.
episodes[].intent body string No Intent of this item.
next body integer No Batch form B — with an anchor episode (episode_id, or film_ref + episode_no), also issue tickets for the next N episodes of the same film. Following episodes are recorded as prefetch. An episode past the end of the film, or not published yet, is a per-item 404 not_ready. Sending next (even 0) switches the response to the batch shape.

Request example

curl -s -X POST https://www.wandura.ai/api/delivery/v1/playback \
  -H "Authorization: Bearer $WANDURA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "film_ref": "the-heiress-returns",
    "episode_no": 1,
    "union_info": "<device ID from the Player SDK>",
    "drm_kek": "<device public key from the Player SDK>",
    "intent": "play",
    "viewer_ref": "u_48213:dev_9a7c"
  }'

Response

200 OK

Field Type Description
vid string Video ID to load in the Player SDK. Present with play_auth_token.
play_auth_token string Play token to hand to the Player SDK. Keep it secret and do not log it.
manifest_url string Signed stream link, present instead of vid + play_auth_token only for content Wandura delivers to your organisation without encryption, by agreement (then protection is signed-url-only). Valid until expires_at.
expires_at string (ISO 8601, UTC) When the ticket expires: 30 minutes after it was issued (15 minutes for manifest_url).
protection string Protection level of what you received. private-encrypted for licensed episodes. Only sell paid episodes when it is private-encrypted.
subtitles array Subtitles attached to the episode, in lang order. Empty when none are attached.
subtitles[].lang string Language code, for example en, vi or pt-BR.
subtitles[].url string URL of the subtitle file. Not tied to the ticket's expiry.
subtitles[].format string Always webvtt.
items array Batch only — one entry per requested episode, in request order. On status 200 the entry also has episode_id, episode_no and the same ticket fields as a single response; otherwise error, error_code, optional reason (403, and 429 prefetch_shed) and retry_after (503 and 429), and no episode data.
items[].request object Echo of what was asked for this item.
items[].status integer This item's own HTTP-equivalent status.
items[].subtitles[].lang string Language code, for example en, vi or pt-BR.
items[].subtitles[].url string URL of the subtitle file. Not tied to the ticket's expiry.
items[].subtitles[].format string Always webvtt.
items[].retry_after integer Seconds to wait before retrying this item (503 and 429).

Response example

{
  "vid": "v0d2f8c1a9e4b7",
  "play_auth_token": "eyJhbGciOi...placeholder...",
  "expires_at": "2026-10-01T12:30:00.000Z",
  "protection": "private-encrypted",
  "subtitles": [
    { "lang": "en", "url": "https://cdn.example.com/subtitles/heiress-ep1-en.vtt", "format": "webvtt" },
    { "lang": "vi", "url": "https://cdn.example.com/subtitles/heiress-ep1-vi.vtt", "format": "webvtt" }
  ]
}

Response headers

Header Responses Description
Wandura-API-Status all API status. beta today, on every response, success or error.
Wandura-Version all The dated API version (YYYY-MM-DD) this response was produced in: the version pinned to your API key, or the one you sent in the Wandura-Version request header. Keys created before dated versions existed use 2026-10-02.
Wandura-Request-Id all An opaque id for this request (req_ followed by 26 characters). Every response carries it, errors included, and error bodies repeat it as request_id. Log it, and quote it when you contact Wandura about a request: it finds the exact request in our logs. Do not parse it.
Cache-Control all Always no-store. Do not cache these responses in shared caches.
Deprecation 200 Present only when this endpoint, or the dated version serving this response, is deprecated (RFC 9745): @<unix seconds> of the deprecation date. Log it in your monitoring.
Sunset 200 Present only when a deprecated endpoint or dated version has a date when it stops working (RFC 8594), as an HTTP date.
Link 200 Present with Deprecation: <https://docs.wandura.ai/…>; rel="deprecation" (and rel="sunset") pointing to the page that explains what to use instead.
Retry-After 429, 503 Seconds to wait before retrying. Sent with 429 and with retryable 503 responses.
X-RateLimit-Limit 429 Requests (for playback, tickets) allowed per window for this endpoint group and your organisation.
X-RateLimit-Window 429 Window length in seconds.

Errors

Status error_code When
400 none The body is not valid JSON, or has neither episode_id nor film_ref with a numeric episode_no.
400 invalid_request Only one of union_info and drm_kek was sent, or a malformed batch (episodes empty or longer than 3, an item without an episode, episodes mixed with top-level episode fields or with next, next outside 0–2 or without an anchor).
400 invalid_device_info A device value looks empty or is too short. The fields array names it.
400 unsupported_version The Wandura-Version header, or the version pinned to your key, is unknown or retired. The body adds supported_versions and latest_version (and upgrade_to when your pin was retired).
401 none The Authorization: Bearer header is missing, or the key is unknown or revoked.
403 none The key lacks the playback scope.
403 not_entitled Your organisation may not stream this film right now. reason is one of org_suspended, entitlement_not_found, entitlement_suspended, streaming_disabled, outside_window.
404 not_ready The film or episode is unknown, not published, or not ready to stream.
409 sdk_required The episode is encrypted and the request had no device values. The body also carries protection.
429 rate_limited The playback rate limit was reached, or (with reason prefetch_shed) prefetch is paused because your playback traffic is close to it. See Retry-After.
502 none The stream could not be prepared or the play token could not be signed. Retry with backoff.
503 not_ready No playable version of the episode is available right now.
503 service_unavailable Wandura could not verify the API key in time. The key is not wrong: retry after Retry-After seconds, with backoff.
503 service_unavailable The video provider did not answer in time (The video provider did not answer in time — retry). The episode is not missing: retry after Retry-After seconds, with backoff.
503 service_unavailable Wandura could not check the licence or the episode in time (Could not check the licence or episode right now — retry). This says nothing about your licence or the episode: keep selling, and retry after Retry-After seconds, with backoff.

Example 403:

{
  "error": "outside_window",
  "error_code": "not_entitled",
  "reason": "outside_window"
}

What to do in each case: Errors.

Updated 2 Oct 2026 · Change history (8)

↑↓ selectEnter openEsc close

Send feedback

Your message goes straight to the Wandura team. It includes the address of this page.

0 / 4000