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:
- If your catalog is a few hundred films or fewer, call
GET /catalogwithoutlimit: one response is one consistent snapshot. - If you page, de-duplicate by film
id(the later copy wins). - Apply withdrawals only after a complete walk: every page answered
200. A walk that failed part-way changes nothing. - 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" }(nodata) on creatives errors. Creatives429and400unsupported_versionuse the flat format.Branch on
error_code(creatives:code). Unknown codes: decide by status class, as in the retry table above.Retry-Aftercomes with429and with retryable503s.Every response carries a
Wandura-Request-Idheader, and every JSON error body arequest_idfield. 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,
prefetchtickets are refused first (429,"reason": "prefetch_shed", when less than about 20 % of the allowance is left), and aplayrequest may be held up to 0.5 s instead of refused. GET /configreports your current limits for every counter exceptvideo.- 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.
Related
Updated 3 Oct 2026 · Change history (2)