# Catalog

The catalog is the list of films and episodes your organisation may stream right now. It is designed to be the only film list your app needs: it carries display order, free-episode rules, genre facets and a per-episode readiness flag, so you do not have to maintain any of these yourself.

## What is in the catalog

`GET /api/delivery/v1/catalog` returns a film when all of these are true at the moment of the request:

- Your organisation holds a streaming licence for the film, and that licence is active.
- Today falls inside the licence window. Windows are whole days in UTC, and the first and last day are included.
- The film is published.

For those films, it returns every published episode. Films that are still in production, and review cuts shared with you for feedback, never appear in the catalog and cannot be played through the API.

The catalog contains metadata only. It never contains a video URL; you request playback per episode with [`POST /playback`](https://docs.wandura.ai/developers/concepts/playback).

## Response shape

The response has two flat arrays, `films` and `episodes`. Join them on `episodes[].film_id` = `films[].id`. The full field list is in the [catalog reference](https://docs.wandura.ai/developers/reference/catalog).

The whole catalog comes back in one response. There is no pagination.

## Order

`films` arrives sorted by `display_order`, smallest first. Render films in the order you receive them. Wandura edits the order, so do not keep your own ordering table.

`episodes` arrives grouped by film ID, then sorted by `episode_no`. The groups are not in display order, so take the film order from `films`.

## Free and paid episodes

Episodes with `episode_no` less than or equal to the film's `free_until_episode` are free; the rest are paid. The value differs per film and can change, so read it from the catalog rather than hard-coding it. `0` means no free episode; `null` means no number is set for the film, so treat every episode as paid.

## Planned episodes

`planned_episodes` is the number of episodes the film will have. When some of them are not published yet, the catalog still includes a row for each missing episode number so you can show a "coming soon" tile. These placeholder rows have:

| Field | Value |
|---|---|
| `id` | `null` |
| `status` | `"planned"` |
| `stream_ready` | `false` |
| `title`, `synopsis`, `duration_ms`, `poster_uri` | `null` |

Never send a placeholder row to `POST /playback`: it has no episode ID. If a film has more published episodes than `planned_episodes`, all of them are still returned. When `planned_episodes` is `null`, the film has no placeholder rows.

## Readiness: `stream_ready`

`stream_ready: true` means an encrypted version of the episode exists and `POST /playback` can issue a ticket for it. Grey out or hide episodes where it is `false`, rather than letting a viewer tap into an error.

The flag is a snapshot taken when you fetched the catalog. `POST /playback` checks your licence and the episode again every time, and its answer is the one that counts. A `not_ready` answer for an episode that was `stream_ready: true` is normal, for example after a title is withdrawn.

## Genre facets

Each film carries three optional facets for browse screens and filters. `null` means the film has not been classified; show it without that facet rather than guessing.

| Field | Possible values |
|---|---|
| `audience` | `women`, `men`, `lgbtq`, or `null` |
| `form` | `live_action`, `anime`, or `null` |
| `setting` | `modern`, `city`, `fantasy`, `campus`, `palace`, or `null` |

Films also carry `genres`, an array of free-text genre labels, and `language`.

## Text and artwork

- `synopsis_short` is meant for cards and `synopsis_full` for detail pages. Lengths vary between films, so clamp text in your UI. `synopsis_full` can be `null`.
- `episodes[].synopsis` can be `null`. Render nothing when it is.
- `poster_url` (film) and `poster_uri` (episode) can be `null` while artwork is being prepared. Show a placeholder image.

## Keeping your copy fresh

Cache the catalog on your server and refresh it on a schedule, instead of calling the API on every app launch. A licence that ends or a title that is withdrawn disappears from the next response; remove anything that is no longer listed.

The catalog has its own rate-limit counter, separate from playback and creatives. See [Rate limits](https://docs.wandura.ai/developers/concepts/rate-limits).
