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

List licensed catalog

Markdown
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/catalog

Scope: 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.
code string or null Wandura series code, for example WD000016. Stable and shared across every Wandura system (the playlist in your media space has this name). Use it with GET /api/delivery/v1/series/{code}.
title_i18n object Text by language: keys are BCP-47 codes, values are the text, for example {"en": "The Heiress Returns"}. Today Wandura fills en; more languages may appear. A missing key means no text in that language: fall back to en. Empty object when there is no text.
synopsis_short_i18n object Text by language: keys are BCP-47 codes, values are the text, for example {"en": "The Heiress Returns"}. Today Wandura fills en; more languages may appear. A missing key means no text in that language: fall back to en. Empty object when there is no text.
synopsis_i18n object Text by language: keys are BCP-47 codes, values are the text, for example {"en": "The Heiress Returns"}. Today Wandura fills en; more languages may appear. A missing key means no text in that language: fall back to en. Empty object when there is no text.
content_rating string or null Age rating: all, 13+, 16+ or 18+. null when not rated yet.
is_completed boolean or null true when every planned episode is released, false while more are coming, null when the planned count is unknown.
release_date string or null Original release date, YYYY-MM-DD, or null when unknown.
default_lang string or null Audio language of the film as a BCP-47 code (en, vi, pt-BR), or null when unknown. Prefer it over the free-text language.
tags array Coded genre and theme tags. At most one has is_main true (the main genre, shown under the title). Can be empty.
people array Main cast or characters, leads first. Can be empty.
trailer object or null The trailer video, or null. vid is the video ID in Wandura's media space.
metadata_version string or null Hash of the film's metadata (titles, synopses, rating, tags, people, artwork, episodes and their subtitles). Unchanged hash = nothing to update on your side.
metadata_updated_at string (ISO 8601, UTC) or null When metadata_version last changed.
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.
materials array The film's artwork as media assets in YOUR media space (the space your Player SDK and CMS use; Wandura's shared space if your organisation has none): the latest version of each kind. Identify an asset by mid and title (<code>-poster-3-4-v<N>, <code>-cover-9-16-v<N>). url is pre-signed on your playback domain and expires at expires_at; sign store_uri with your own key when you need a fresh link. Empty until Wandura has copied the artwork into your space. images and poster_url stay as they are.

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).
poster_signed_url string or null The cover of this episode's video (vid) in your media space, as a pre-signed https link on your playback domain, valid until poster_expires_at (at most 15 minutes) — download the image, do not store the link. null when there is no such cover yet or Wandura cannot sign for your domain (then sign poster_store_uri yourself). poster_url is not affected.
poster_store_uri string or null Storage URI of the cover of vid in your media space, for signing a fresh link with your own key. null when there is no such cover yet.
poster_expires_at integer or null When poster_signed_url stops working, in Unix epoch seconds (at most 15 minutes ahead). null when poster_signed_url is null.
vid string or null Video ID of the episode in YOUR media space — the ID your app loads in the Player SDK and your CMS signs a play token for. If your organisation has its own media space it is the ID of Wandura's copy there (null until that copy is ready); otherwise it is the ID in Wandura's shared space. null on placeholder rows.
vid_ready boolean or null true when vid is ready to play in your media space. false while Wandura is still copying the episode (and on placeholder rows). null only if Wandura could not tell, so keep your previous value.
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.
title_i18n object Text by language: keys are BCP-47 codes, values are the text, for example {"en": "The Heiress Returns"}. Today Wandura fills en; more languages may appear. A missing key means no text in that language: fall back to en. Empty object when there is no text.
subtitles array Subtitle languages available for the episode (the files come with the playback ticket). Empty on placeholder rows.
subtitles[].lang string BCP-47 code, for example en.

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",
      "code": "WD000016",
      "title_i18n": { "en": "The Heiress Returns" },
      "synopsis_short_i18n": { "en": "Cast out on her wedding day, a disowned heiress comes back to take everything." },
      "synopsis_i18n": { "en": "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." },
      "content_rating": "13+",
      "is_completed": false,
      "release_date": null,
      "default_lang": "en",
      "tags": [
        { "slug": "revenge", "name": "Revenge", "name_i18n": { "en": "Revenge" }, "type": "genre", "is_main": true },
        { "slug": "romance", "name": "Romance", "name_i18n": { "en": "Romance" }, "type": "genre", "is_main": false },
        { "slug": "heiress", "name": "Heiress", "name_i18n": { "en": "Heiress" }, "type": "theme", "is_main": false }
      ],
      "people": [{ "name": "Mia", "role": "lead", "description": "A disowned heiress back for what is hers.", "avatar_url": null }],
      "trailer": null,
      "metadata_version": "9a0b1c2d3e4f5061728394a5b6c7d8e9",
      "metadata_updated_at": "2026-10-03T09:00:00.000Z",
      "images": [
        { "kind": "poster_3_4", "lang": null, "url": "https://cdn.example.com/posters/the-heiress-returns-3x4.jpg", "width": 1080, "height": 1440 },
        { "kind": "cover_9_16", "lang": null, "url": "https://cdn.example.com/posters/the-heiress-returns.jpg", "width": 1080, "height": 1920 }
      ],
      "materials": [
        {
          "kind": "poster_3_4",
          "title": "WD000016-poster-3-4-v1",
          "mid": "v0d2f8c1a9e4b7c3d5e6f708192a3b4c5",
          "url": "https://your-playback.example.com/materials/WD000016-poster-3-4-v1.jpg?auth_key=1791262800-9f1c2e3d4a5b6c7d-0-3c5e7a9b1d2f4a6c8e0b2d4f6a8c0e2b",
          "expires_at": 1791262800,
          "store_uri": "tos-media-space/materials/WD000016-poster-3-4-v1.jpg",
          "width": 1080,
          "height": 1440
        },
        {
          "kind": "cover_9_16",
          "title": "WD000016-cover-9-16-v1",
          "mid": "v0d2f8c1a9e4b7c3d5e6f708192a3b4c6",
          "url": "https://your-playback.example.com/materials/WD000016-cover-9-16-v1.webp?auth_key=1791262800-0a1b2c3d4e5f6071-0-7e9a1c3e5a7c9e1b3d5f7a9c1e3b5d7f",
          "expires_at": 1791262800,
          "store_uri": "tos-media-space/materials/WD000016-cover-9-16-v1.webp",
          "width": 1080,
          "height": 1920
        }
      ]
    }
  ],
  "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,
      "title_i18n": { "en": "Episode 1" },
      "subtitles": [{ "lang": "en" }],
      "poster_url": null,
      "poster_signed_url": "https://your-playback.example.com/oMOAomiBCg7rGAYIP1f6x4yG7vI2QSgnGaqKs4?auth_key=1791262800-5c6d7e8f9a0b1c2d-0-1f3b5d7f9b1d3f5b7d9f1b3d5f7b9d1f",
      "poster_store_uri": "tos-media-space/oMOAomiBCg7rGAYIP1f6x4yG7vI2QSgnGaqKs4",
      "poster_expires_at": 1791262800,
      "vid": "v25ec7gm0018db1l8tqtgd60242f05e0",
      "vid_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,
      "poster_url": null,
      "poster_signed_url": null,
      "poster_store_uri": null,
      "poster_expires_at": null,
      "vid": null,
      "vid_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,
      "poster_url": null,
      "poster_signed_url": null,
      "poster_store_uri": null,
      "poster_expires_at": null,
      "vid": null,
      "vid_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 7 Oct 2026 · Change history (10)

↑↓ selectEnter openEsc close

Send feedback

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

0 / 4000