# Get playlist metadata

For video partners. Returns one playlist for your CMS: Category, group, title, description, artwork, and its clips in order with title, description, duration, muscle groups, equipment, difficulty, calories, poster and production order code. When your organisation is licensed for the playlist, it also says where your copies live in your media space. Call it when [List playlists](https://docs.wandura.ai/developers/reference/playlists-list) shows a `content_version` you have not stored.

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

**Scope:** `metadata:read`

## Description
Any playlist in the library can be read, licensed or not. When `granted` is `true`, `media` names your media space and the playlist in it, and each copied clip carries its video ID (`vid`) and `format` (`hls` or `mp4`). When `granted` is `false`, `media` is `null` and every clip has `ready: false` with no video ID. There are no playback links: your app plays the copies straight from your [own media space](https://docs.wandura.ai/developers/concepts/own-space).

A playlist that does not exist and one that is no longer in the library (withdrawn, or its Category withdrawn) return the same `404`. A key of an organisation that licenses films gets `403` `4032`; use [Get series metadata](https://docs.wandura.ai/developers/reference/series-get) instead.

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).

## 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 | Playlist code, for example `wall-pilates-01` (the `code` from 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/playlists/wall-pilates-01 \
  -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 playlist (`data`)
| Field | Type | Description |
|---|---|---|
| `code` | string | Playlist code. |
| `status` | string | Always `active` (a playlist not in the library returns 404). |
| `title` | string | Playlist title. |
| `description` | string or null | Playlist description, or `null`. |
| `category` | object | The Category of the playlist (for example Fitness). |
| `category.slug` | string | Stable code of the Category, for example `fitness`. Also the `category` filter of the list. |
| `category.name` | string | Display name. |
| `group` | object or null | Filter label of the playlist inside its Category (for example Pilates or Gym), or `null`. It is not a unit of licence. |
| `group.code` | string | Stable code. |
| `group.title` | string | Display name. |
| `updated_at` | integer | Last change in Unix epoch seconds. |
| `content_version` | string | Hash of this whole response. Store it; when the list shows the same value there is nothing to fetch. |
| `metadata_version` | string or null | Hash of the playlist metadata alone (the same for every organisation). |
| `granted` | boolean | `true` when your organisation is licensed for this playlist. |
| `media` | object or null | Where your copies live, or `null` when the playlist is not granted. Playback stays between your app and your media space; this API never returns playback links. |
| `media.space` | string or null | Your media space for this Category, or `null` while it is not set up. |
| `media.playlist_id` | string or null | ID of the playlist in `space` (named after `code`), or `null` until it exists. |
| `media.playlist_name` | string or null | Name of that playlist, or `null`. |
| `media.playlists` | array | Every playlist holding the clips in order (at most 100 videos each, so a longer playlist continues in `<code>-2`…). |
| `images` | array | Artwork of the playlist. Today one `cover` (the poster of the first clip); more kinds may be added. Empty when there is none. |
| `images[].kind` | string | `cover`. New kinds may be added. |
| `images[].source` | string | `media_space` or `wandura_cdn`. New values may be added. |
| `images[].url` | string or null | Image URL when `source` is `wandura_cdn`, else `null`. Download it and host your own copy. |
| `images[].expires_at` | integer or null | When `url` stops working, in Unix epoch seconds, or `null`. |
| `images[].space` | string or null | Your media space holding the image, or `null`. |
| `images[].uri` | string or null | The image's URI inside `space`, or `null`. |
| `clips` | array | Clips in playlist order. |
| `clips[].position` | integer | Position in the playlist. Clips come back in this order. |
| `clips[].id` | string (UUID) | Clip ID. It never changes; sync on it. The same clip can sit in several playlists. |
| `clips[].title` | string | Clip title. |
| `clips[].description` | string or null | Short description, or `null`. |
| `clips[].duration_ms` | integer or null | Duration in milliseconds. |
| `clips[].muscle_groups` | array of strings | Muscle groups worked. Can be empty. |
| `clips[].equipment` | array of strings | Equipment needed. Can be empty. |
| `clips[].difficulty` | string or null | `beginner`, `intermediate` or `advanced`, or `null`. |
| `clips[].kcal_est` | integer or null | Estimated calories burned. |
| `clips[].order_code` | string or null | Production order code (for example `ABS_01_E01`), matching your order file character for character, or `null`. |
| `clips[].poster` | object or null | An image, or `null` when there is none. `source` says where to get it. `media_space`: the image is already in your own media space (`space`) under `uri` (for a clip it is also the poster set on its `vid`); load it through your space as you load your videos. `wandura_cdn`: a link valid until `expires_at`; download the file and host your own copy, do not store the link. New sources may be added: skip a value you do not know. |
| `clips[].poster.source` | string | `media_space` (in your media space) or `wandura_cdn` (a Wandura link to download). New values may be added. |
| `clips[].poster.url` | string or null | Image URL when `source` is `wandura_cdn`, else `null`. |
| `clips[].poster.expires_at` | integer or null | When `url` stops working, in Unix epoch seconds, or `null`. |
| `clips[].poster.space` | string or null | Your media space holding the image when `source` is `media_space`, else `null`. |
| `clips[].poster.uri` | string or null | The image's URI inside `space` when `source` is `media_space`, else `null`. |
| `clips[].ready` | boolean | `true` when the clip is copied and playable in your media space (`media.space`). |
| `clips[].vid` | string or null | Video ID of the clip in your media space, or `null` while it is not copied (or the playlist is not granted). |
| `clips[].format` | string or null | How the copy is packaged: `hls` (clips of 30 seconds or more) or `mp4` (shorter clips). `null` while not copied. |
| `clips[].playlist_id` | string or null | ID of the playlist in your media space that holds this clip, or `null`. |
| `clips[].playlist_name` | string or null | Name of that playlist, or `null`. |

### Images
Each image (the playlist `cover` in `images[]`, and each clip's `poster`) says in `source` where to get it. `media_space`: the image is already in your media space (`space`, `uri`); load it through your space as you load your videos. `wandura_cdn`: `url` is a link valid for 1 hour (until `expires_at`); download the file and host your own copy, never store the link. An image that is not available is `null`.

## Response example
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "code": "wall-pilates-01",
    "status": "active",
    "title": "Wall Pilates",
    "description": "Ten low-impact moves using a wall for support.",
    "category": { "slug": "fitness", "name": "Fitness" },
    "group": { "code": "pilates", "title": "Pilates" },
    "updated_at": 1790390000,
    "content_version": "5d41402abc4b2a76b9719d911017c592",
    "metadata_version": "0cc175b9c0f1b6a831c399e269772661",
    "granted": true,
    "media": {
      "space": "your-fitness-space",
      "playlist_id": "9f8e7d6c5b4a3921",
      "playlist_name": "wall-pilates-01",
      "playlists": [{ "id": "9f8e7d6c5b4a3921", "name": "wall-pilates-01" }]
    },
    "images": [{ "kind": "cover", "source": "media_space", "url": null, "expires_at": null, "space": "your-fitness-space", "uri": "your-fitness-space/fit-3f2a1b4c.jpg" }],
    "clips": [
      {
        "position": 1,
        "id": "3f2a1b4c-5d6e-4f70-8a9b-0c1d2e3f4a5b",
        "title": "Wall Squat",
        "description": "Slide down the wall and hold.",
        "duration_ms": 45000,
        "muscle_groups": ["quads", "glutes"],
        "equipment": ["wall"],
        "difficulty": "intermediate",
        "kcal_est": 9,
        "order_code": "ABS_01_E01",
        "poster": { "source": "media_space", "url": null, "expires_at": null, "space": "your-fitness-space", "uri": "your-fitness-space/fit-3f2a1b4c.jpg" },
        "ready": true,
        "vid": "v0a1b2c3d4e5f6",
        "format": "hls",
        "playlist_id": "9f8e7d6c5b4a3921",
        "playlist_name": "wall-pilates-01"
      }
    ]
  },
  "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. |
| 403 | 4032 | Playlist metadata is for video partners; your organisation licenses films (use the series endpoints). |
| 404 | 4040 | No such playlist, or it is not in the library (the same answer on purpose). |
| 429 | none | Rate limit reached. |
| 503 | 5030 | The playlist 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#playlists-get-errors).
