# List open videos

> **This path is frozen.** It keeps working and apps already on it need change nothing, but Wandura is no longer adding to it. New integrations should use [your own video space](https://docs.wandura.ai/developers/concepts/own-space), where your app reads straight from the video platform in your organisation's space.

Returns a page of the open videos granted to your organisation, each with signed MP4 links valid for 1 hour. Walk every page and cache the result on your server. See [Open video](https://docs.wandura.ai/developers/concepts/open-video) and [Open video sync](https://docs.wandura.ai/developers/guides/open-video-sync).

```
GET /api/delivery/v1/videos
```

**Scope:** `videos:read`, on a key bound to an app

## Request parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Authorization` | header | string | Yes | `Bearer <API key>` for a key with the `videos:read` scope. |
| `collection` | query | string | No | Limit the page to one collection, by its code. |
| `cursor` | query | string | No | Opaque token from a previous `next_cursor`. Do not construct or parse it. |
| `limit` | query | integer | No | Rows per page, 1 to 200. Default `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`. |

## Request example
```bash
curl -s "https://www.wandura.ai/api/delivery/v1/videos?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 |
|---|---|---|
| `rows` | array | Videos on this page, in collection order. |
| `next_cursor` | string or null | Pass it as `cursor` for the next page. `null` means you have everything. |
| `expires_at` | string (ISO 8601, UTC) | When every link on this page stops working (1 hour after the response). |
| `refresh_before` | string (ISO 8601, UTC) | Call the list again before this time (45 minutes after the response). |
| `links` | object | Ready-made absolute URLs — follow `links.next` instead of building the next-page URL. |
| `links.self` | string | This request. |
| `links.next` | string or null | The next page, or `null` on the last page. |

### Fields of a video (`rows[]`)
| Field | Type | Description |
|---|---|---|
| `id` | string (UUID) | Video ID. It never changes; sync on it. |
| `slug` | string | URL-safe video identifier. |
| `title` | string | Video title. |
| `order_code` | string or null | Production order code (for example `ABS_01_E01`), matching your order file character for character, or `null` for a video that is not in it. Use it to join with the order file once. |
| `collection` | string | Code of the collection this row comes from. A video in two granted collections appears once under each. |
| `position` | integer | Position of the video in its collection. Rows come back in collection order. |
| `duration_ms` | integer or null | Duration in milliseconds. |
| `muscle_groups` | array of strings | Muscle groups worked. Can be empty. |
| `equipment` | array of strings | Equipment needed. Can be empty. |
| `difficulty` | string or null | Difficulty label. |
| `kcal_est` | integer or null | Estimated calories burned. |
| `mp4` | object | Signed MP4 links, valid until `expires_at`. Play them as received; never cache them in your app. |
| `mp4.p480` | string | 480p MP4 link. Play this by default. |
| `mp4.p720` | string or null | 720p MP4 link, or `null` until that rendition exists. |

Stop walking when `next_cursor` is `null`, never on a short or empty page: videos not ready to play are skipped.

## Response example
```json
{
  "rows": [
    {
      "id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "slug": "core-abs-01",
      "title": "Core abs 01",
      "order_code": "ABS_01_E01",
      "collection": "ABS_01",
      "position": 1,
      "duration_ms": 42000,
      "muscle_groups": ["abs"],
      "equipment": [],
      "difficulty": "beginner",
      "kcal_est": 12,
      "mp4": { "p480": "https://cdn.example.com/v/core-abs-01-480.mp4?sign=example", "p720": null }
    }
  ],
  "next_cursor": null,
  "expires_at": "2026-10-01T13:00:00.000Z",
  "refresh_before": "2026-10-01T12:45:00.000Z"
}
```

## 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 | none | `cursor` is not a token this endpoint issued, or `limit` is not an integer between 1 and 200. |
| 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 `videos:read` scope. |
| 403 | none (`error` is `app_not_registered`) | The key is not bound to an app. Register the app on the Developers page and use that app's key. |
| 403 | none (`error` is `org_suspended`) | Your organisation's access is suspended. |
| 404 | none (`error` is `collection_not_found`) | The collection is unknown, not published, or not granted to your organisation. |
| 429 | `rate_limited` | The rate limit for this endpoint was reached. |
| 500 | none | The video list could not be read. Retry with backoff. |
| 503 | `service_unavailable` | Wandura could not verify the API key in time. The key is not wrong: retry after `Retry-After` seconds, with backoff. |

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