# List playlists

For video partners. Returns a page of the playlists in Wandura's video library, each with a `content_version`. Use it in a CMS sync job: when `content_version` equals the value you stored, nothing changed and you can skip [Get playlist metadata](https://docs.wandura.ai/developers/reference/playlists-get).

```
GET /api/delivery/v1/playlists
```

**Scope:** `metadata:read`

## Description
Lists every playlist in the library, oldest change first: all Categories, or one Category with `category`. You see the whole library, not only what you are licensed for: `granted` says which playlists your organisation has. Pass `updated_since` (Unix epoch seconds) to get only the playlists changed after that moment. A playlist Wandura takes out of the library — the playlist itself, or its whole Category — stays in the list as `withdrawn` for 30 days, so your sync job sees it; on a withdrawn row `clip_count` and `content_version` are `null`.

This endpoint is read-only and returns no playback links: your app plays the copies in your [own media space](https://docs.wandura.ai/developers/concepts/own-space). A key of an organisation that licenses films gets `403` `4032`; use [List series](https://docs.wandura.ai/developers/reference/series-list) 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. |
| `category` | query | string | No | Optional. Category slug, for example `fitness`. Omitted = every Category. A Category taken out of the library less than 30 days ago still works here (its playlists come back `withdrawn`). |
| `updated_since` | query | integer | No | Optional. Unix epoch seconds; only playlists changed after this moment are returned. |
| `limit` | query | integer | No | Optional. Items per page, 1 to 100. Default 50. |
| `cursor` | query | string | No | Optional. `next_cursor` of the previous page, passed back unchanged. |
| `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`. |

Keep the same `updated_since` and `category` on every page of one sync, and follow `next_cursor` until it is `null`. A full page always carries a `next_cursor`, so the last page can be empty.

## Request example
```bash
curl -s "https://www.wandura.ai/api/delivery/v1/playlists?category=fitness&updated_since=1790390000&limit=50" \
  -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.next_cursor` | string or null | Pass it back as `cursor`; `null` on the last page. |
| `server_time` | integer | Server time in Unix epoch seconds. |
| `links` | object | Ready-made absolute URLs of a list response. Follow them instead of building URLs. |

### Fields of a playlist (`data.items[]`)
| Field | Type | Description |
|---|---|---|
| `code` | string | Playlist code, for example `wall-pilates-01`. It never changes. |
| `status` | string | `active`: in the library now. `withdrawn`: Wandura took the playlist out of the library; it stays in this list for 30 days so your sync job sees it. New values may be added. |
| `title` | string | Playlist title. |
| `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. |
| `clip_count` | integer or null | Clips in the playlist. `null` when withdrawn. |
| `granted` | boolean | `true` when your organisation is licensed for this playlist (its clips are copied into your media space). You see the whole library; `false` playlists carry no video IDs. |
| `updated_at` | integer | Last change in Unix epoch seconds (metadata, your licence, or copies in your media space). |
| `content_version` | string or null | Hash of everything GET /playlists/{code} returns for you. Unchanged = no need to fetch the detail. `null` when withdrawn. |
| `withdrawn_at` | integer or null | When it was withdrawn (epoch seconds), or `null`. |

## Response example
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "items": [
      {
        "code": "wall-pilates-01",
        "status": "active",
        "title": "Wall Pilates",
        "category": { "slug": "fitness", "name": "Fitness" },
        "group": { "code": "pilates", "title": "Pilates" },
        "clip_count": 24,
        "granted": true,
        "updated_at": 1790390000,
        "content_version": "5d41402abc4b2a76b9719d911017c592",
        "withdrawn_at": null
      },
      {
        "code": "gym-basics-02",
        "status": "active",
        "title": "Gym Basics",
        "category": { "slug": "fitness", "name": "Fitness" },
        "group": { "code": "gym", "title": "Gym" },
        "clip_count": 18,
        "granted": false,
        "updated_at": 1790391000,
        "content_version": "7d793037a0760186574b0282f2f435e7",
        "withdrawn_at": null
      }
    ],
    "next_cursor": null
  },
  "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 Category. |
| 422 | 4220 | `limit` is not 1 to 100, `updated_since` is not epoch seconds, `category` is not a slug, or `cursor` is not a value this endpoint returned. |
| 429 | none | Rate limit reached. |
| 503 | 5030 | The list 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-errors).
