On this page
Returns every film and episode your organisation may stream right now. The response is metadata only and never contains a video URL; request playback per episode with Request playback.
GET /api/delivery/v1/catalogScope: metadata:read
Description
A film is included when your organisation has an active streaming licence for it, today (UTC) is inside the licence window, and the film is published. For each included film, all published episodes are returned, plus a placeholder row for each planned episode that is not published yet. Films arrive sorted by display_order. Without limit and cursor you receive the whole catalog in one response; with limit you receive pages of films, each with all their episodes, until next_cursor is null. Pages are not one consistent snapshot: see Canonical API behaviour · Pagination before you page. See Catalog.
Request parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Authorization |
header | string | Yes | Bearer <API key> for a key with the metadata:read scope. |
limit |
query | integer | No | Optional. Films per page, 1 to 200. Omit it (and cursor) to receive the whole catalog in one response. |
cursor |
query | string | No | Optional. The next_cursor (or the cursor inside links.next) of the previous page. Opaque; pass it back unchanged. With a cursor and no limit the page size is 50. |
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. |
Request example
curl -s https://www.wandura.ai/api/delivery/v1/catalog \
-H "Authorization: Bearer $WANDURA_API_KEY"Response
200 OK with an object containing two arrays.
Film fields (films[])
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Film ID. It never changes: use it as your primary key, and as film_ref in playback. |
title |
string | Film title. |
slug |
string | URL-safe film identifier, unique across films. Wandura does not change it after the film is created. Also accepted as film_ref in playback. |
synopsis_short |
string or null | Short description for cards. Length varies. |
synopsis_full |
string or null | Full description for detail pages. |
genres |
array of strings | Genre labels. Can be empty. |
language |
string or null | Language of the film, as free text written by Wandura's content team (for example en or English). It is not a fixed list of codes: display it, do not parse it. |
poster_url |
string or null | Film poster URL. |
status |
string | Always published. |
display_order |
integer | Display position, smallest first. The array is already sorted by it. |
planned_episodes |
integer or null | Total number of episodes planned. null when not set: then the film gets no placeholder rows, and episodes lists only its published episodes. |
free_until_episode |
integer or null | Episodes with episode_no up to and including this number are free. 0 means no free episode. null means no number is set for the film: treat every episode as paid, and ask Wandura if you expected free episodes. |
audience |
string or null | women, men, lgbtq, or null if unclassified. New values may be added. |
form |
string or null | live_action, anime, or null if unclassified. New values may be added. |
setting |
string or null | modern, city, fantasy, campus, palace, or null if unclassified. New values may be added. |
images |
array | Artwork for the film, one entry per kind Wandura has. A kind Wandura does not have is left out (never a null entry), so the array can be empty. Every url is an absolute https URL on a Wandura host that you can load directly. poster_url stays as it is. |
Episode fields (episodes[])
| Field | Type | Description |
|---|---|---|
id |
string (UUID) or null | Episode ID. Use it as episode_id in playback. null on placeholder rows. |
film_id |
string (UUID) | ID of the film the episode belongs to. |
episode_no |
integer | Episode number, starting at 1. |
title |
string or null | Episode title. |
synopsis |
string or null | Episode description. Often null. |
duration_ms |
integer or null | Duration in milliseconds. |
poster_uri |
string or null | Episode thumbnail reference. Not guaranteed to be a directly loadable URL — use poster_url. |
poster_url |
string or null | The episode image as an absolute https URL you can load directly, or null (then use the film's poster_url). |
status |
string | published, or planned for a placeholder row. |
stream_ready |
boolean | true when a ticket can be issued for the episode. Always false on placeholder rows. |
Within a film, each episode_no appears once, so film_id + episode_no identifies an episode. Episodes are grouped by film ID and sorted by episode_no within each film. Take the film order from films.
If your organisation has no current licences, both arrays are empty.
Response example
{
"films": [
{
"id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
"title": "The Heiress Returns",
"slug": "the-heiress-returns",
"synopsis_short": "Cast out on her wedding day, a disowned heiress comes back to take everything.",
"synopsis_full": "Cast out on her wedding day, Mia loses her family, her name and her fortune. Five years later she returns with a new identity and one goal.",
"genres": ["romance", "revenge"],
"language": "en",
"poster_url": "https://cdn.example.com/posters/the-heiress-returns.jpg",
"status": "published",
"display_order": 10,
"planned_episodes": 3,
"free_until_episode": 1,
"audience": "women",
"form": "live_action",
"setting": "modern",
"images": [
{ "kind": "poster_3_4", "url": "https://cdn.example.com/posters/the-heiress-returns-3x4.jpg", "width": 1080, "height": 1440 },
{ "kind": "cover_9_16", "url": "https://cdn.example.com/posters/the-heiress-returns.jpg", "width": null, "height": null }
]
}
],
"episodes": [
{
"id": "b2a4d6e8-1c3f-4a5b-8d7e-9f0a1b2c3d4e",
"film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
"episode_no": 1,
"title": "Episode 1",
"synopsis": null,
"duration_ms": 94000,
"poster_uri": null,
"status": "published",
"stream_ready": true
},
{
"id": "c7d9e1f3-5a6b-4c8d-9e0f-1a2b3c4d5e6f",
"film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
"episode_no": 2,
"title": "Episode 2",
"synopsis": null,
"duration_ms": 101000,
"poster_uri": null,
"status": "published",
"stream_ready": false
},
{
"id": null,
"film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
"episode_no": 3,
"title": null,
"synopsis": null,
"duration_ms": null,
"poster_uri": null,
"status": "planned",
"stream_ready": false
}
]
}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 | invalid_request |
limit is not a whole number from 1 to 200, or cursor is not a value this endpoint returned. |
| 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 metadata:read scope. |
| 403 | none (error is org_suspended) |
Your organisation's access is suspended. |
| 429 | rate_limited |
The rate limit for the catalog was reached. |
| 503 | service_unavailable |
Wandura could not verify the API key or read the catalog in time. Nothing is wrong on your side: retry after Retry-After seconds, with backoff. |
The two 403 responses carry no error_code. Tell them apart by error: it is exactly org_suspended when your organisation is suspended, and any other text means the key lacks metadata:read. This is the one case where you compare error; compare the whole string.
What to do in each case: Errors.
Updated 3 Oct 2026 · Change history (7)