On this page
- Error formats
- Flat format
- Creatives envelope
- Request id
- Handle errors by code
- Retry-After
- Messages for viewers
- Every endpoint
- Every path
- Catalog
- List series
- Get series metadata
- List playlists
- Get playlist metadata
- Playback
- Configuration
- List creatives
- Get a creative
- Download a creative's final file
- List open videos
- Get one open video
- Related
TL;DR — Branch on
error_code(creatives: the numericcode), never on the message. Keep a default branch: an unknown code is handled by its HTTP status class (429and5xxretry, other4xxdo not). This page lists every status and code each endpoint returns, with what your app shows and what your backend does. When to retry, and how: the retry policy.
Error formats
Flat format
Catalog, playback, configuration and open video use a flat object:
{
"error": "Episode not found or not ready to stream",
"error_code": "not_ready"
}| Field | Type | Description |
|---|---|---|
error |
string | A message for your logs. Do not show it to viewers and do not parse it, except where a row below says the code is in error. |
error_code |
string | The machine-readable code. Branch on it. Absent on a few errors (shown as none below). |
reason |
string | With not_entitled, and with 429 prefetch_shed: the detail. |
fields |
array of strings | With invalid_device_info: the device values that were rejected. |
protection |
string | With sdk_required: the protection level of the episode. |
supported_versions, latest_version, upgrade_to |
With unsupported_version: what you can send instead. |
A few 4xx answers carry no error_code and put a stable code in error instead: org_suspended, app_not_registered, collection_not_found and video_not_found. Compare the whole string.
Creatives envelope
The creatives endpoints wrap errors in an envelope with a numeric code. Errors have no data field:
{
"code": 4011,
"message": "Invalid or revoked API key",
"server_time": 1790679032
}code is four digits: the first three are the HTTP status. Two creatives answers use the flat format instead: 429 rate_limited and 400 unsupported_version.
Request id
Every response carries a Wandura-Request-Id header (for example req_01J9Z3W2N8V4K7D5QX6T0B1C2M), and every JSON error body, flat or envelope, a request_id field with the same value. Log it, and quote it when you contact support: it finds the exact request in Wandura's logs.
Handle errors by code
| You received | Do |
|---|---|
| A code listed on this page | Follow its row. |
An unknown error_code with 429 |
Wait Retry-After, then retry. |
An unknown error_code with another 4xx |
Do not retry. Show a generic error and alert your engineers. |
An unknown error_code with a 5xx |
Retry with backoff, like 503. |
| No body, or a body that is not JSON | Decide by status class, the same way. |
New codes can appear within v1 at any time; an app without a default branch shows a blank screen when one does. Never show error or message to viewers: map codes to your own text.
Retry-After
Sent with every 429, and with the 503s marked "after Retry-After" below. Never retry sooner. 503 not_ready, creatives 503s, 502 and 500 carry none: use backoff.
Messages for viewers
The "Your app shows" column below uses these four messages. Use your own wording.
| Message | When |
|---|---|
| Unavailable — "This title is no longer available" | The licence is not active. |
| Not yet — "Not available yet" | The episode is not playable right now. |
| Busy — "Busy, try again in a moment" | Retried and still failing temporarily. |
| Broken — "Couldn't load this episode" | A problem in your integration. Alert your engineers. |
Every endpoint
Every path
| Status | Code | When | Retry? | Do |
|---|---|---|---|---|
| 404 | not_found |
Unknown path under /api/delivery/v1/, any method. No Wandura-Version header. |
No | Fix the path. |
| 405 | none (empty body) | A real path with the wrong method, for example GET /playback. The Allow header lists the right one. |
No | Fix the method. |
Catalog
GET /api/delivery/v1/catalog
| Status | Code | When | Retry? | Your app shows | Your backend does |
|---|---|---|---|---|---|
| 400 | invalid_request |
limit is not 1–200, or cursor was changed. |
No | — | Fix the request. Restart the walk from page one. |
| 400 | unsupported_version |
The Wandura-Version header, or your key's pin, is unknown or retired. |
No | — | Send a supported version; see Migrating versions. |
| 401 | none | Header missing, or key unknown or revoked. | No | — | Check the key. Keep your previous catalog. |
| 403 | none | The key lacks metadata:read. |
No | — | Use a key with that scope. |
| 403 | org_suspended (in error) |
Your organisation is suspended. | No | — | Keep your copy, stop selling, contact Wandura. |
| 429 | rate_limited |
Catalog limit reached. | After Retry-After |
— | Wait, then find the loop. |
| 503 | service_unavailable |
Wandura could not check the key in time. The key is fine. | After Retry-After |
— | Retry. Never rotate the key for this. |
| 503 | service_unavailable |
Wandura could not read the catalog in time (Could not read the catalog in time — retry). |
After Retry-After |
— | Retry. Never treat it as an empty catalog. |
List series
GET /api/delivery/v1/series
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks metadata:read. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 422 | 4220 |
limit is not 1–100, updated_since is not epoch seconds, or cursor is not a value this endpoint returned. |
No | Fix the request; restart from your last checkpoint. |
| 429 | rate_limited (flat) |
Catalog limit reached (shared with the catalog). | After Retry-After |
Wait; sync less often. |
| 503 | 5030 |
The list is temporarily unavailable. | Backoff | Retry; keep your checkpoint. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. Do not rotate the key. |
Get series metadata
GET /api/delivery/v1/series/{code}
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks metadata:read. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 404 | 4040 |
No such series, or your organisation may not stream it (the same answer on purpose). | No | Stop showing it; check the list. |
| 429 | rate_limited (flat) |
Catalog limit reached (shared with the catalog). | After Retry-After |
Wait. |
| 503 | 5030 |
The series is temporarily unavailable. | Backoff | Retry. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. |
List playlists
GET /api/delivery/v1/playlists
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks metadata:read. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 403 | 4032 |
Your organisation licenses films, not video. | No | Use List series. |
| 404 | 4040 |
The category is not in the library. |
No | Check the Category slug. |
| 422 | 4220 |
limit is not 1–100, updated_since is not epoch seconds, category is not a slug, or cursor is not a value this endpoint returned. |
No | Fix the request; restart from your last checkpoint. |
| 429 | rate_limited (flat) |
Catalog limit reached (shared with the catalog). | After Retry-After |
Wait; sync less often. |
| 503 | 5030 |
The list is temporarily unavailable. | Backoff | Retry; keep your checkpoint. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. Do not rotate the key. |
Get playlist metadata
GET /api/delivery/v1/playlists/{code}
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks metadata:read. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 403 | 4032 |
Your organisation licenses films, not video. | No | Use Get series metadata. |
| 404 | 4040 |
No such playlist, or it is no longer in the library (the same answer on purpose). | No | Stop showing it; check the list. |
| 429 | rate_limited (flat) |
Catalog limit reached (shared with the catalog). | After Retry-After |
Wait. |
| 503 | 5030 |
The playlist is temporarily unavailable. | Backoff | Retry. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. |
Playback
POST /api/delivery/v1/playback. In a batch, the request-level rows answer the whole request; the per-ticket rows come back inside items[], each with its own status (and retry_after instead of the header).
Whole request
| Status | Code | When | Retry? | Your app shows | Your backend does |
|---|---|---|---|---|---|
| 400 | none | The body is not JSON, or has neither episode_id nor film_ref with a numeric episode_no. |
No | Broken | Fix the request. |
| 400 | invalid_request |
Only one of union_info and drm_kek, or a malformed batch. |
No | Broken | Fix the request. |
| 400 | invalid_device_info |
A device value is empty-looking, for example "(null)", or under 8 characters. fields names it. |
No | Broken | Fix how the app reads the Player SDK values. Alert. |
| 400 | unsupported_version |
Unknown or retired dated version. | No | Broken | See Migrating versions. |
| 401 | none | Header missing, or key unknown or revoked. | No | Broken | Check the key. Alert. |
| 403 | none | The key lacks playback. |
No | Broken | Use a playback key. |
| 429 | rate_limited |
Playback limit reached for every item. With "reason": "prefetch_shed": prefetch is paused near the limit. |
After Retry-After |
Busy (play); nothing (prefetch) | Retry plays; drop prefetches. See Rate limits. |
| 503 | service_unavailable |
Wandura could not check the key in time. | After Retry-After |
Busy | Retry. Do not rotate the key. |
| 500 | none (rare) | An unexpected error. The JSON body carries request_id. |
Backoff | Busy | Retry once; report it with the request id. |
Per ticket (top level for a single episode, or one item of a batch)
| Status | Code | When | Retry? | Your app shows | Your backend does |
|---|---|---|---|---|---|
| 403 | not_entitled |
Your organisation may not stream this film right now. reason says why (table below). |
No | Unavailable | Log reason. Stop selling the film once your next catalog sync confirms it is gone. |
| 404 | not_ready |
The episode or film is unknown, not published or not ready. | No | Not yet | Refresh your catalog. |
| 409 | sdk_required |
Encrypted episode and no device values. | No | Broken | Send union_info and drm_kek. Alert. |
| 429 | rate_limited |
This item could not be admitted (batch only). reason prefetch_shed for a paused prefetch. |
After retry_after |
Nothing for a prefetch | Retry this item only. |
| 502 | none | The stream could not be prepared, or the play token could not be signed. | Backoff | Busy | Retry, then report. |
| 503 | not_ready |
No playable version of the episode right now. No Retry-After. |
Not now | Not yet | Try again later (next play or sync). |
| 503 | service_unavailable |
The video provider did not answer in time (The video provider did not answer in time — retry). |
After Retry-After (2 s) |
Busy | Retry. The episode is not missing. |
| 503 | service_unavailable |
The ticket could not be issued right now (Could not issue this grant right now — retry). |
After Retry-After |
Busy | Retry. |
Do not take irreversible steps on one answer. Before you stop selling a film or refund viewers because of
not_entitledornot_ready, confirm it with your next catalog sync: a withdrawn film disappears from the catalog.
If Wandura cannot check your licence or the episode in time while issuing a ticket, it answers 503 service_unavailable (Could not check the licence or episode right now — retry) with Retry-After, never 404 or 403. Keep selling, and retry.
not_entitled reasons
Log reason and quote it to support, but show the same screen for all of them: new reasons may be added.
reason |
Meaning |
|---|---|
org_suspended |
Your organisation's access is suspended. |
entitlement_not_found |
Your organisation has no licence for this film. |
entitlement_suspended |
Your licence for this film is suspended. |
streaming_disabled |
Your licence for this film does not include streaming. |
outside_window |
Today (UTC) is outside your licence window. |
Configuration
GET /api/delivery/v1/config
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | none | Header missing, or key unknown or revoked. | No | Check the key. |
| 403 | org_suspended (in error) |
Your organisation is suspended. | No | Contact Wandura. |
| 429 | rate_limited |
Configuration limit reached. | After Retry-After |
Read it less often: at start-up or every few hours. |
| 503 | service_unavailable |
Wandura could not check the key in time. | After Retry-After |
Retry; keep the configuration you had. |
| 503 | service_unavailable |
Wandura could not read your configuration in time (Could not read your configuration in time — retry). |
After Retry-After |
Retry; keep the configuration you had. |
List creatives
GET /api/delivery/v1/creatives
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks creatives:export. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 422 | 4220 |
limit is not 1–100 (an empty limit= counts as 0). |
No | Fix the request. |
| 422 | 4221 |
updated_since is not epoch seconds. |
No | Fix the request. |
| 422 | 4222 |
cursor is not valid. |
No | Restart from your last checkpoint. |
| 429 | rate_limited (flat) |
Creatives limit reached. | After Retry-After |
Wait; sync less often. |
| 503 | 5030 |
The feed is temporarily unavailable. | Backoff | Retry; keep your checkpoint. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. Do not rotate the key. |
Get a creative
GET /api/delivery/v1/creatives/{id}
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks creatives:export. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 404 | 4040 |
No approved creative with this ID for your organisation (also: the ID is not a UUID). | No | Remove it from your active set. |
| 429 | rate_limited (flat) |
Creatives limit reached (shared with the list). | After Retry-After |
Wait. |
| 503 | 5030 |
The feed is temporarily unavailable. | Backoff | Retry. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. |
Download a creative's final file
GET /api/delivery/v1/creatives/{id}/file
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | unsupported_version (flat) |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | 4010 |
No Authorization: Bearer header. |
No | Fix the request. |
| 401 | 4011 |
Key unknown or revoked. | No | Check the key. |
| 403 | 4030 |
The key lacks creatives:export. |
No | Use a key with that scope. |
| 403 | 4031 |
Your organisation is suspended. | No | Contact Wandura. |
| 403 | 4032 |
rights_ended: your export rights for the film have ended. |
No | Stop using the creative. |
| 404 | 4040 |
No approved creative with this ID for your organisation (also: the ID is not a UUID). | No | Remove it from your active set. |
| 409 | 4090 |
final_not_ready: the final file is not ready yet. |
Later | Retry when the record's final.ready is true. |
| 429 | rate_limited (flat) |
Creatives limit reached (shared with the other creatives endpoints). | After Retry-After |
Wait. |
| 503 | 5030 |
Creative data is temporarily unavailable. | Backoff | Retry. |
| 503 | 5031 |
Wandura could not check the key in time. | Backoff | Retry. |
| 503 | 5032 |
The download link could not be prepared. | Backoff | Retry. |
List open videos
GET /api/delivery/v1/videos
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | none | cursor is not a token this endpoint issued, or limit is not 1–200. |
No | Fix the request; restart the sweep. |
| 400 | unsupported_version |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | none | Header missing, or key unknown or revoked. | No | Check the key. |
| 403 | none | The key lacks videos:read. |
No | Use the app's open video key. |
| 403 | app_not_registered (in error) |
The key is not bound to an app. | No | Register the app; use that app's key. |
| 403 | org_suspended (in error) |
Your organisation is suspended. | No | Contact Wandura. |
| 404 | collection_not_found (in error) |
Unknown, unpublished or not granted to you. | No | Check the collection code. |
| 429 | rate_limited |
List limit reached. | After Retry-After |
Wait; cache the list. |
| 500 | none | The list could not be read. | Backoff | Retry; keep serving your previous list. |
| 503 | service_unavailable |
Wandura could not check the key in time. | After Retry-After |
Retry. |
Get one open video
GET /api/delivery/v1/videos/{id}
| Status | Code | When | Retry? | Your backend does |
|---|---|---|---|---|
| 400 | none | id is not a UUID. |
No | Use the id from the list. |
| 400 | unsupported_version |
Unknown or retired dated version. | No | Send a supported version. |
| 401 | none | Header missing, or key unknown or revoked. | No | Check the key. |
| 403 | none | The key lacks videos:read. |
No | Use the app's open video key. |
| 403 | app_not_registered (in error) |
The key is not bound to an app. | No | Register the app. |
| 403 | org_suspended (in error) |
Your organisation is suspended. | No | Contact Wandura. |
| 404 | video_not_found (in error) |
Unknown, not granted, unpublished, archived or not playable yet. | No | Drop it from your list. |
| 429 | rate_limited |
Single-video limit reached (its own counter). | After Retry-After |
Wait; prefer the cached list. |
| 500 | none | The video could not be read. | Backoff | Retry. |
| 503 | service_unavailable |
Wandura could not check the key in time. | After Retry-After |
Retry. |
Related
- Canonical API behaviour: retry policy, rate limits, freshness.
- Troubleshooting: from a symptom to a fix.
- Support: what to send when an error persists.
Updated 19 Oct 2026 · Change history (11)