Bản lưu ngày / Snapshot of 2026-10-17 · 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

Canonical API behaviour

Markdown
On this page

TL;DR — This page is the contract. Every number and rule that more than one page relies on lives here once: authentication, pagination, ticket lifetime and reuse, how fresh each answer is, the retry policy, errors, rate limits and versioning. Other pages explain and link here; if two pages ever disagree, this page wins.

Authentication

Rule Value
Header Authorization: Bearer <API key> on every request. No cookies, no query-string keys.
Key format wnd_live_ followed by 32 characters.
Who it acts for The whole organisation that owns the key. There is no per-viewer identity in the API.
Where it may live Your servers only. The API sends no CORS headers.
Transport https://www.wandura.ai, TLS 1.2 or later.
Order of checks Wandura-Version header → key present → key valid → pinned version → scope → organisation → rate limit → request body.
Scope per endpoint One table, on Authentication.

A bad Wandura-Version header is answered 400 before the key is even looked at. A key Wandura cannot check in time answers 503 service_unavailable: the key is fine, retry it. Key creation, rotation and revocation timings are on Security.

Pagination

Three endpoints return pages. Each follows the same rules: the cursor is opaque (copy it, never build or decode it), and you stop when the next cursor is null, never because a page is short or empty.

Endpoint Page size Next page Unit of a page
GET /catalog Optional: limit 1–200. No limit and no cursor = the whole catalog in one response. A cursor without limit = 50. next_cursor (present only when paging) and links.next Films; each page carries all the episodes of its films, placeholders included.
GET /creatives limit 1–100, default 50 data.next_cursor, links.next Creative records, oldest update first.
GET /videos limit 1–200, default 50 next_cursor, links.next Open videos. A page can be short or empty while more follow.

Catalog pages are not one snapshot

Each catalog page is cut from the catalog as it is at the moment that page is served. If Wandura publishes or withdraws a film, or a licence starts or ends, while you walk the pages, a film can be skipped or appear twice across pages. Sync safely:

  1. If your catalog is a few hundred films or fewer, call GET /catalog without limit: one response is one consistent snapshot.
  2. If you page, de-duplicate by film id (the later copy wins).
  3. Apply withdrawals only after a complete walk: every page answered 200. A walk that failed part-way changes nothing.
  4. Mark a film unavailable only when it was missing from two complete walks in a row. A film skipped once by a moving catalog comes back on the next walk.

The catalog sync guide implements exactly this.

The creatives feed has no such gap for unchanged records: it is ordered by update time then ID, so new updates land after the pages you have read. Records updated during your walk appear again on the next sync.

Playback tickets

Lifetime

What you received Lives for Exact deadline
play_auth_token (encrypted episodes, Player SDK) 1,800 s (30 minutes) expires_at
manifest_url (only content delivered without encryption, by agreement) 900 s (15 minutes) expires_at

A ticket is never extended, and once issued it cannot be recalled: it plays until expires_at even if the licence ends or the key is revoked a second later. GET /config gives both lifetimes for your key (playback.grant_ttl_seconds, playback.manifest_url_ttl_seconds).

One ticket per device

A ticket is bound to the device values it was issued for (union_info and drm_kek). It decrypts only on that device. Never hand a ticket to another device, and never cache one under a key that does not include the device.

Reuse

Reusing a ticket is allowed and encouraged, under these rules:

Rule
Key of the reuse viewer + device + episode + intent class (play or prefetch)
How long Until 5 minutes before expires_at. After that, request a new ticket.
On Wandura's side When you send viewer_ref, a repeat request with the same viewer_ref, the same device values, the same episode (identified the same way: episode_id, or film_ref + episode_no) and the same intent class, while the earlier ticket has at least 5 minutes left, returns that same ticket (same token, same expires_at). It is not counted toward your rate limit and is not a new play. Wandura re-checks your licence before returning it.
On your side You may also keep tickets on your backend under the same key, and hand them out again.
Best effort Server-side reuse is an optimisation, not a promise: a repeat request can still return a fresh ticket. Never rely on getting the same token back.
Without viewer_ref Every request mints a new ticket.

Views are counted from play requests. When a viewer starts an episode, always send a request with "intent": "play" for it, even if your app starts the player with a ticket it prefetched earlier. Prefetched tickets are not views.

Batches

One request can ask for up to 3 tickets: episodes (1–3 items) or an anchor episode plus next (0–2). Each ticket counts 1 toward the playback limit. Each item has its own status; items are admitted one by one, prefetch items last. The whole request is 429 only when no item can be issued. Details: Request playback.

Freshness: how old an answer can be

Wandura serves from short caches, so a change on Wandura's side reaches you after a bounded delay. These are the upper bounds.

Change on Wandura's side Visible in Visible in ticket requests
Film or episode published or withdrawn, stream_ready changed Catalog: ≤ 30 s ≤ 60 s
Licence granted, suspended, switched off or past its window Catalog: ≤ 30 s ≤ 30 s
Organisation suspended about 1 s (always ≤ 30 s) about 1 s (always ≤ 30 s)
Key revoked or rotated about 1 s (always ≤ 30 s) about 1 s (always ≤ 30 s)
Something just created (a key, an episode, a licence) ≤ 5 s ≤ 5 s
Subtitle attached or replaced — ≤ 5 minutes
Rate limit raised for your organisation about 1 minute about 1 minute
Subtitle languages in GET /config ≤ 5 minutes —
  • Several servers. Each Wandura server keeps its own cache, so two calls a few seconds apart can return slightly different snapshots. That is normal.
  • During a Wandura database incident the catalog and licence checks are served from the last good answer for up to 5 minutes (stale-if-error), instead of failing. A change Wandura has already seen (a withdrawal, a suspension) is never undone by this.
  • Tickets already issued are never recalled: they play until expires_at (≤ 30 minutes).

What to cache on your side, and for how long: Caching.

Retries

The one retry policy for every Wandura call. The examples implement it.

Answer Retry? How
Network error, timeout Yes Backoff below
429 (any endpoint, any reason) Yes After Retry-After
503 service_unavailable, creatives 5030 / 5031 Yes After Retry-After if present, else backoff
500, 502 Yes Backoff
503 not_ready Not now The episode has no playable version right now. Show "not available yet"; try again on the next play or the next catalog sync.
400, 401, 403, 404, 405, 409, 422 No The same request will fail again: fix the request, the key or the licence.
A 4xx you do not recognise No
A 5xx you do not recognise Yes Like 503

Backoff. When Retry-After is present, wait that many seconds plus a random 0–1 s. Otherwise wait a random time between 0 and min(cap, base × 2^attempt) (full jitter).

Interactive (a viewer is waiting) Background (sync jobs)
Base / cap 0.5 s / 8 s 1 s / 30 s
Timeout per attempt 8 s (never below 5 s: Wandura may hold a play request up to 0.5 s) 15 s
Retries after the first attempt at most 3 at most 5
Total time 15 s, then show Retry 2 minutes, then give up and keep your previous data

Retry budget. Across a process, retries may be at most 10 % of the requests of the last minute (never fewer than 10 a minute). When the budget is spent, fail fast instead of adding load.

Concurrency. Walk pages one at a time, never in parallel. Retry only the failed items of a batch, each after its own retry_after. A retried ticket request with the same viewer_ref and device values usually returns the same ticket, so it does not count twice.

Errors

  • Two formats: a flat { "error", "error_code" } on catalog, playback, config and videos; an envelope { "code", "message", "server_time" } (no data) on creatives errors. Creatives 429 and 400 unsupported_version use the flat format.

  • Branch on error_code (creatives: code). Unknown codes: decide by status class, as in the retry table above.

  • Retry-After comes with 429 and with retryable 503s.

  • Every response carries a Wandura-Request-Id header, and every JSON error body a request_id field. Quote it to support.

Every status and code, per endpoint, with what to do: Errors.

Rate limits

Limits are per organisation (not per key), per counter, with the unit shown.

Counter Endpoints Limit per organisation Unit How it refills
playback POST /playback 10,800 per 60 s tickets: a batch of 3 = 3; a malformed body = 1; a reused ticket = 0; a refused ticket = 0 Continuously: 180 per second, up to 10,800 at once from idle
catalog GET /catalog 3,000 per 60 s requests Continuously: 50 per second
creatives GET /creatives, GET /creatives/{id} (shared) 3,000 per 60 s requests Clock minute (UTC)
videos GET /videos 3,000 per 60 s requests Clock minute (UTC)
video GET /videos/{id} 10,800 per 60 s requests Clock minute (UTC)
config GET /config 600 per 60 s requests Clock minute (UTC)
  • Only requests with a valid key are counted.
  • Counting is approximate on the high side by up to about 2.5 %.
  • Close to the playback limit, prefetch tickets are refused first (429, "reason": "prefetch_shed", when less than about 20 % of the allowance is left), and a play request may be held up to 0.5 s instead of refused.
  • GET /config reports your current limits for every counter except video.
  • Wandura can raise any limit for your organisation on request.

How it behaves under load, and what to do: Rate limits.

Versioning

Rule
Major version In the path: /v1.
Dated version Each key is pinned to the dated version current when it was created (today the only one: 2026-10-02). Every response says which one it used in Wandura-Version. Send Wandura-Version: <date> to try another version for one request.
Rotation Keeps the key's pin. A new key is pinned to the latest version.
Inside /v1 Only additive changes: new endpoints, optional parameters, fields, values and error codes. Ignore what you do not recognise.
Breaking change A new dated or major version. The old major version keeps working 12 months after the new one is announced; during the beta, a breaking change inside /v1 is announced 30 days ahead (urgent security fixes excepted).
Status header Wandura-API-Status: beta on every response.

Policy and deprecation headers: Versioning and API evolution.

Caching headers

Every Delivery API response, tickets included, carries Cache-Control: no-store, and sets no cookies. Never store any of them in a shared cache (a CDN or a proxy): tickets, signed links and your catalog belong to your organisation only.

Updated 7 Oct 2026 · Change history (2)

↑↓ selectEnter openEsc close

Send feedback

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

0 / 4000