# Get series metadata

Returns everything Wandura knows about one series for your CMS: titles and synopses by language, coded genre and theme tags, age rating, main cast or characters, artwork with sizes, audio language, completion flag, and the published episodes with their video ID and subtitle files. Call it when [List series](https://docs.wandura.ai/developers/reference/series-list) shows a `content_version` you have not stored.

```
GET /api/delivery/v1/series/{code}
```

**Scope:** `metadata:read`

## Description
Missing values are `null` or empty: they never block a licence. A series that does not exist and a series you may not stream return the same `404`, so you cannot tell the two apart. Successful responses are wrapped in the `{code, message, data, server_time}` envelope; errors use the same envelope without `data`, except `429` and `400` `unsupported_version`, which use the flat format. This endpoint shares its rate-limit counter with the [catalog](https://docs.wandura.ai/developers/reference/catalog).

Your CMS may override any field. Wandura never overwrites your edits.

## Request parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Authorization` | header | string | Yes | `Bearer <API key>` for a key with the `metadata:read` scope. |
| `code` | path | string | Yes | Series code, for example `WD000016` (the `code` from the catalog or the list). |
| `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`. |

## Request example
```bash
curl -s https://www.wandura.ai/api/delivery/v1/series/WD000016 \
  -H "Authorization: Bearer $WANDURA_API_KEY"
```

## Response
`200 OK`. New fields may be added at any time: ignore the ones you do not recognise.

| Field | Type | Description |
|---|---|---|
| `code` | integer | `0`. |
| `message` | string | `"ok"`. |
| `data` | object |  |
| `server_time` | integer | Server time in Unix epoch seconds. |
| `links` | object | Ready-made absolute URL of this resource (an API link, not a media file). |

### Fields of a series (`data`)
| Field | Type | Description |
|---|---|---|
| `code` | string | Series code, for example `WD000016`. |
| `status` | string | Always `active` (a series you may not stream returns 404). |
| `updated_at` | integer | Last change in Unix epoch seconds. |
| `content_version` | string or null | Hash of this whole response. Store it; when the list shows the same value there is nothing to fetch. |
| `language` | string or null | Audio language, BCP-47 (same as `default_lang`). |
| `default_lang` | string or null | Audio language, BCP-47. |
| `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. |
| `genres` | array of strings | Free-text genre labels (legacy). Prefer `tags`. |
| `poster` | object or null | The 9:16 cover as `{url}`, or `null`. The same image appears in `images` with its size. |
| `content_rating` | string or null | Age rating, or `null` when not rated. |
| `is_completed` | boolean or null | `true` when every planned episode is released. |
| `release_date` | string or null | Original release date, or `null`. |
| `planned_episodes` | integer or null | Total episodes planned, or `null`. |
| `tags` | array |  |
| `tags[].slug` | string | Stable code of the tag, for example `romance` or `enemies-to-lovers`. Map it to your own tag list by this value, not by `name`. |
| `tags[].name` | string | English display name. |
| `tags[].name_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. |
| `tags[].type` | string | `genre`, `theme` or `system`. New types may be added. |
| `tags[].is_main` | boolean | `true` for the main genre of the film (at most one). |
| `people` | array |  |
| `people[].name` | string | Name of the actor or character. |
| `people[].role` | string | `lead` or `support`. |
| `people[].description` | string or null | One-line description, or `null`. |
| `people[].avatar_url` | string or null | Absolute `https` image URL, or `null`. |
| `images` | array |  |
| `images[].kind` | string | What the image is for: `poster_3_4` (3:4 card poster), `cover_9_16` (9:16 full-screen cover, the same image as `poster_url`), `hero_16_9` (16:9 banner) or `title_treatment` (the title as artwork). New kinds may be added: skip a kind you do not know. |
| `images[].lang` | string or null | Language of text burned into the image (BCP-47), or `null` when the image suits every language. Prefer the entry matching your user's language, else the `null` one. |
| `images[].url` | string | Absolute `https` URL of the image on a Wandura host. Load it directly; it needs no signing. |
| `images[].width` | integer or null | Width in pixels, measured from the file. `null` when Wandura has not recorded the size. |
| `images[].height` | integer or null | Height in pixels, measured from the file. `null` when Wandura has not recorded the size. |
| `trailer` | object or null | The trailer video ID in Wandura's media space, or `null`. Always `null` when you have your own space. |
| `metadata_version` | string or null | Hash of the film metadata alone (same value as in the catalog). |
| `metadata_updated_at` | integer or null | When `metadata_version` last changed (epoch seconds). |
| `media` | object | Where the videos live: `space` is the media space (yours when you have one) and `playlist_name` is the playlist holding the episodes in order (equal to `code`). |
| `episodes` | array | Published episodes, by `ep_number`. |
| `episodes[].ep_number` | integer | Episode number, starting at 1. The playlist order follows it. |
| `episodes[].vid` | string or null | Video ID of the episode in the media space named in `media.space` (your own space when you have one). `null` while the episode is not in that space yet. |
| `episodes[].ready` | boolean | `true` when the episode can be played from that space. |
| `episodes[].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. |
| `episodes[].duration_sec` | integer or null | Duration in seconds, for cross-checking only. |
| `episodes[].subtitles[].lang` | string | BCP-47 code. |
| `episodes[].subtitles[].url` | string | WebVTT file URL. Download it and host your own copy. |
| `episodes[].subtitles[].updated_at` | integer | Last change of the file (epoch seconds). |

Image and subtitle URLs are absolute `https` URLs on a Wandura host. Download subtitle files and host your own copy.

## Response example
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "code": "WD000016",
    "status": "active",
    "updated_at": 1790390000,
    "content_version": "c3f1a9e07b2d4c5e8f9a0b1c2d3e4f50",
    "language": "en",
    "default_lang": "en",
    "title_i18n": { "en": "The Hidden Alpha Bride" },
    "synopsis_short_i18n": { "en": "Rejected by her mate, a runaway omega returns as the bride of his rival." },
    "synopsis_i18n": { "en": "Rejected by her mate on the night of the bond, Luna runs. Three years later she returns as the bride of his rival." },
    "genres": ["Werewolf", "Romance"],
    "poster": { "url": "https://static.example.com/wd000016/cover.jpg" },
    "content_rating": "13+",
    "is_completed": true,
    "release_date": null,
    "planned_episodes": 60,
    "tags": [
      { "slug": "werewolf", "name": "Werewolf", "name_i18n": { "en": "Werewolf" }, "type": "genre", "is_main": true },
      { "slug": "fated-mates", "name": "Fated Mates", "name_i18n": { "en": "Fated Mates" }, "type": "theme", "is_main": false }
    ],
    "people": [{ "name": "Luna", "role": "lead", "description": "A rejected omega who returns to claim her place.", "avatar_url": null }],
    "images": [
      { "kind": "poster_3_4", "lang": null, "url": "https://static.example.com/wd000016/poster-3x4.jpg", "width": 1080, "height": 1440 },
      { "kind": "cover_9_16", "lang": null, "url": "https://static.example.com/wd000016/cover.jpg", "width": 1080, "height": 1920 }
    ],
    "trailer": null,
    "metadata_version": "9a0b1c2d3e4f5061728394a5b6c7d8e9",
    "metadata_updated_at": 1790380000,
    "media": { "space": "your-space", "playlist_name": "WD000016" },
    "episodes": [
      {
        "ep_number": 1,
        "vid": "v0d2f8c1a9e4b7",
        "ready": true,
        "title_i18n": { "en": "The Night of the Bond" },
        "duration_sec": 137,
        "subtitles": [{ "lang": "en", "url": "https://static.example.com/wd000016/1.en.vtt", "updated_at": 1790380000 }]
      }
    ]
  },
  "server_time": 1790400000
}
```

## Response headers
| Header | Responses | Description |
|---|---|---|
| `Wandura-API-Status` | all | API status. `beta` today, on every response, success or error. |
| `Wandura-Version` | 200 | 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` | 200 | 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` | 200 | Always `no-store`. Do not cache these responses in shared caches. |
| `Retry-After` | 429 | Seconds to wait before retrying. Sent with 429 and with retryable 503 responses. |

## Errors
| Status | `code` | When |
|---|---|---|
| 400 | none | The `Wandura-Version` header, or the version pinned to your key, is unknown or retired. |
| 401 | 4010 | The `Authorization: Bearer` header is missing. |
| 401 | 4011 | The key is unknown or revoked. |
| 403 | 4030 | The key lacks the `metadata:read` scope. |
| 403 | 4031 | Your organisation's access is suspended. |
| 404 | 4040 | No such series, or your organisation may not stream it (the same answer on purpose). |
| 429 | none | Rate limit reached. |
| 503 | 5030 | The series is temporarily unavailable. |
| 503 | 5031 | Wandura could not verify the API key in time. |

What to do for each: [Errors](https://docs.wandura.ai/developers/concepts/errors#series-get-errors).
