On this page
TL;DR — There is no sandbox: tests call
https://www.wandura.ai. Keep test traffic in your test organisation (sample films only, plays never counted) and trigger any error on demand with theWandura-Simulateheader. Run your CI against a mock built from the OpenAPI file. Never load-test production. Then run the test plan below on real phones.
Where to test
| Layer | Use | Counts as views? |
|---|---|---|
| Unit and CI tests | A mock server generated from the OpenAPI file, for example prism mock https://docs.wandura.ai/developers/openapi/delivery-v1.json, or the fakes in the examples |
No (nothing reaches Wandura) |
| Integration tests | Your test organisation's keys, against production | No |
| Acceptance on real phones | Your test organisation, then a final check in production | Production: yes |
Test organisation
Every partner gets one test organisation next to its production one. Wandura creates it on request in your team's Discord channel and adds your Admins and Developers to it. On partner.wandura.ai, switch to "Your organisation (test)" and create keys under Developers as usual. The examples on this page call such a key WANDURA_TEST_KEY.
Test keys call the same production URL, https://www.wandura.ai, and behave exactly like production keys, with three differences:
- Not counted. Its plays are never counted as views, usage or revenue.
- Sample films only. It can only stream Wandura's sample films
ninety-daysandthe-fire-clause(short episodes, with English subtitles). Open video is not available in a test organisation. - Simulated errors. It may send the
Wandura-Simulateheader (below). Production keys ignore that header completely.
GET /config tells you which kind of key you hold:
{
"key": { "scopes": ["metadata:read", "playback"], "test_organisation": true }
}Real states, on request. For what simulation cannot show (for example a key that really stops working, or a licence that really ends), ask in your channel: Wandura can suspend your test organisation, suspend or end the licence of a sample film, switch streaming off, or lower your playback limit to test 429 for real. Changes take effect within 30 seconds. The test organisation's playback limit is 600 tickets per minute.
Simulated errors
With a test key, send Wandura-Simulate: <scenario>. You get the same status, body and headers as the real error, plus Wandura-Simulated: <scenario> on the response. A simulated answer issues no ticket and does not count against your limits; simulated answers are themselves limited to 300 per minute per organisation. The key, scope and version checks still run first, so a bad key still answers 401. If the response has no Wandura-Simulated header, nothing was simulated (unknown name, or a scenario that does not apply to that endpoint) and the request ran normally.
| Scenario | Endpoints | You get |
|---|---|---|
rate_limited |
playback, catalog, config | 429 rate_limited with Retry-After, X-RateLimit-Limit, X-RateLimit-Window |
prefetch_shed |
playback | 429 rate_limited, "reason": "prefetch_shed" |
service_unavailable |
playback, catalog, config | 503 service_unavailable, Retry-After: 1 |
service_unavailable:provider |
playback | 503 service_unavailable (video provider), Retry-After: 2 |
service_unavailable:catalog |
catalog | 503 service_unavailable (Could not read the catalog in time — retry) |
server_error |
playback, catalog, config | 500 with a JSON body carrying request_id, as for a real unexpected error |
unsupported_version |
playback, catalog, config | 400 unsupported_version with supported_versions, latest_version, upgrade_to |
not_entitled |
playback | 403 not_entitled, reason entitlement_not_found |
not_entitled:<reason> |
playback | 403 not_entitled with reason entitlement_suspended, streaming_disabled, outside_window or org_suspended |
org_suspended |
playback, catalog, config | Playback: as not_entitled:org_suspended; catalog and config: 403 with error org_suspended |
not_ready |
playback | 404 not_ready |
not_ready:no_rendition |
playback | 503 not_ready |
sdk_required |
playback | 409 sdk_required with protection |
partial_batch |
playback, batch of 2–3 | Item 0 is a real ticket (not counted, as in any test organisation); later items 503 with retry_after: 2, or 429 prefetch_shed |
empty_catalog |
catalog | 200 with empty films and episodes |
version_deprecated |
catalog, config | A real 200 plus Deprecation, Sunset and Link headers |
In a batch, a per-episode scenario answers 200 { "items": [...] } with the error on each item, exactly like a real batch.
What cannot be simulated: an expired Player SDK licence (it lives on the device).
Example: rate_limited
curl -s -i https://www.wandura.ai/api/delivery/v1/catalog \
-H "Authorization: Bearer $WANDURA_TEST_KEY" \
-H "Wandura-Simulate: rate_limited"HTTP/2 429
retry-after: 1
x-ratelimit-limit: 600
x-ratelimit-window: 60
wandura-simulated: rate_limited{
"error": "Too many catalog requests for this account. This is an abuse ceiling, not a quota — if a normal integration reaches it, contact us and it will be raised.",
"error_code": "rate_limited"
}Example: not_entitled:outside_window
No device values are needed: nothing is issued.
curl -s https://www.wandura.ai/api/delivery/v1/playback \
-H "Authorization: Bearer $WANDURA_TEST_KEY" \
-H "Content-Type: application/json" \
-H "Wandura-Simulate: not_entitled:outside_window" \
-d '{ "film_ref": "ninety-days", "episode_no": 1, "intent": "play" }'{
"error": "outside_window",
"error_code": "not_entitled",
"reason": "outside_window"
}Example: partial_batch
Item 0 is a real ticket, so send the device values of a real device (see Quickstart · Part B).
curl -s https://www.wandura.ai/api/delivery/v1/playback \
-H "Authorization: Bearer $WANDURA_TEST_KEY" \
-H "Content-Type: application/json" \
-H "Wandura-Simulate: partial_batch" \
-d '{ "film_ref": "ninety-days", "episode_no": 1, "next": 2, "intent": "play",
"union_info": "'"$UNION_INFO"'", "drm_kek": "'"$DRM_KEK"'", "viewer_ref": "qa-viewer-1:device-1" }'{
"items": [
{ "request": { "film_ref": "ninety-days", "episode_no": 1 }, "status": 200, "vid": "v0d2f8c1a9e4b7", "play_auth_token": "placeholder-play-token", "expires_at": "2026-10-02T12:30:00.000Z", "protection": "private-encrypted", "subtitles": [] },
{ "request": { "film_ref": "ninety-days", "episode_no": 2 }, "status": 503, "error": "The video provider did not answer in time — retry", "error_code": "service_unavailable", "retry_after": 2 },
{ "request": { "film_ref": "ninety-days", "episode_no": 3 }, "status": 429, "error": "Prefetch requests are paused while your playback traffic is close to its ceiling — tickets for viewers who pressed play are still issued. Retry the prefetch after Retry-After, or request it when the viewer plays.", "error_code": "rate_limited", "reason": "prefetch_shed", "retry_after": 1 }
]
}What you can reproduce with any key
| Scenario | How |
|---|---|
401 |
No key, or a revoked one (refused within about a second of revoking). |
403 scope |
A key with only metadata:read, calling POST /playback. |
400 invalid_device_info |
A device value of "(null)" or under 8 characters. |
400 invalid_request |
Only one device value, or a malformed batch. |
409 sdk_required |
Neither device value. |
404 not_ready |
An unknown episode_id, or next: 2 on the last episode. |
400 unsupported_version |
Wandura-Version: 1999-01-01. |
Creatives 404 4040, 422 |
An unknown ID; a bad limit or cursor. |
| Expired ticket | Pause playback for more than 30 minutes. |
Load tests
Do not load-test production. To test your own system under load, mock Wandura. To test 429 handling against Wandura, use Wandura-Simulate: rate_limited, or ask for a lower limit on your test organisation.
Test plan
Run it on a release build, on a real iPhone and a real Android phone.
Keys and security
- No
wnd_live_in the built apps. - No full key,
play_auth_tokenor raw device value in your logs (Observability). - Catalog without a key:
401; with a key lackingmetadata:read:403. Your job alerts and keeps its copy.
Catalog
- The films shown match the licences Wandura confirmed, in the API's order.
- Free episodes open without payment; the next one shows your paywall.
- Placeholders show "coming soon" and cannot be tapped;
stream_ready: falseepisodes are greyed out. - With the sync job's network blocked for one run, the catalog in the app does not change.
Playback
- A free episode plays, with subtitles; switching language works; an episode without subtitles plays.
- A locked episode shows your paywall and makes no Wandura request.
- Starting an episode sends one request (
play+ unlocked prefetches); moving on starts the next at once and sends a newplay. - Pausing more than 30 minutes, then resuming, leads to one fresh ticket and playback from the same position.
- Airplane mode mid-episode and back: recovery without a crash.
-
429,503,500,not_entitled,not_readyandpartial_batch, simulated withWandura-Simulate, each show the right screen.
Rights ending
- Ask Wandura to end the licence of one sample film in your test organisation. After the next two syncs it is hidden, and a ticket request answers
403not_entitled.
Related
Updated 2 Oct 2026 · Change history (2)