# API reference

The Wandura Delivery API lets your backend read the films you are licensed for, request playback for one episode at a time, and sync approved ad creatives. This section documents each endpoint. For the integration flow, start with the [Quickstart](https://docs.wandura.ai/developers/quickstart), [Authentication](https://docs.wandura.ai/developers/authentication) and [Playback](https://docs.wandura.ai/developers/concepts/playback).

## Base URL

```
https://www.wandura.ai
```

Always call `www.wandura.ai`. The bare domain `wandura.ai` answers every request with a `308` redirect to `www`, and some HTTP clients drop the body or headers of a `POST` when they follow a redirect.

## Endpoints

| Method | Path | Scope | Page |
|---|---|---|---|
| `GET` | `/api/delivery/v1/catalog` | `metadata:read` | [List licensed catalog](https://docs.wandura.ai/developers/reference/catalog) |
| `POST` | `/api/delivery/v1/playback` | `playback` | [Request playback](https://docs.wandura.ai/developers/reference/playback) |
| `GET` | `/api/delivery/v1/creatives` | `creatives:export` | [List creatives](https://docs.wandura.ai/developers/reference/creatives-list) |
| `GET` | `/api/delivery/v1/creatives/{id}` | `creatives:export` | [Get a creative](https://docs.wandura.ai/developers/reference/creatives-get) |

Any other path under `/api/delivery/v1/`, with any method, returns `404` with a flat JSON body and `error_code` `not_found`. If you get it on a path you expected to work, check the path for a typo.

## Server-to-server only

Call these endpoints from your backend, never from a browser or a mobile app. They do not send CORS headers, and your API key must never ship inside a client.

## Authentication

Send your API key as a bearer token on every request:

```
Authorization: Bearer <your API key>
```

Each key belongs to one organisation and carries one or more scopes. A missing, unknown or revoked key gets `401`. A valid key that lacks the endpoint's scope gets `403`.

| Scope | Grants | How you get it |
|---|---|---|
| `metadata:read` | [List licensed catalog](https://docs.wandura.ai/developers/reference/catalog) | Create it yourself in the partner portal |
| `creatives:export` | [List creatives](https://docs.wandura.ai/developers/reference/creatives-list), [Get a creative](https://docs.wandura.ai/developers/reference/creatives-get) | Create it yourself in the partner portal |
| `webhooks:manage` | Not used by any endpoint yet | Create it yourself in the partner portal |
| `playback` | [Request playback](https://docs.wandura.ai/developers/reference/playback) | Create it yourself in the partner portal (**Stream films in an app**) |

Organisation members with the **Admin** or **Developer** role create, rotate and revoke keys on the **Developers** page of the partner portal. The full key is shown once, at creation. This includes keys with the `playback` scope: if one leaks, anyone can stream your licensed films, so revoke it immediately. See [Authentication](https://docs.wandura.ai/developers/authentication).

## Versioning

The API version is part of the path (`/v1`). Within a version, Wandura may add new response fields and new error codes without notice. Ignore fields you do not recognise, and always keep a default branch for unknown error codes. What counts as breaking, and how long old versions are supported: [Versioning and deprecation](https://docs.wandura.ai/developers/concepts/versioning).

Creative records also carry a `schema_version` field, currently `1`. Additive fields do not change it.

### Beta
The API is in **beta**. Every response from `/v1` carries the header `Wandura-API-Status: beta`. During the beta Wandura may still change behaviour inside `/v1`; changes are listed in the **Changelog** (link at the bottom of every page). Spotted something wrong or missing? Use **Send feedback** at the top of any docs page.

## Requests and responses

- Request and response bodies are JSON. Send `Content-Type: application/json` on `POST`.
- Film, episode and creative IDs are UUID strings.
- Playback returns `expires_at` as an ISO 8601 timestamp in UTC. The creatives endpoints return timestamps as Unix epoch seconds.
- The creatives endpoints send `Cache-Control: no-store`. Do not cache their responses in shared caches.

## Pagination

Only [List creatives](https://docs.wandura.ai/developers/reference/creatives-list) is paginated. It uses an opaque `cursor` and returns `next_cursor` until there are no more records. [List licensed catalog](https://docs.wandura.ai/developers/reference/catalog) returns your whole catalog in one response.

## Errors

The API uses two error formats. The catalog and playback endpoints return a flat object:

```json
{
  "error": "Episode not found or not ready to stream",
  "error_code": "not_ready"
}
```

| Field | Type | Description |
|---|---|---|
| `error` | string | Human-readable message. Do not parse it. One exception: a catalog `403` has no `error_code`, and its `error` is exactly `org_suspended` when your organisation is suspended. See [List licensed catalog](https://docs.wandura.ai/developers/reference/catalog#errors). |
| `error_code` | string | Machine-readable code. Branch on this. Absent on some errors, for example `401`. |
| `reason` | string | Present with `not_entitled`. The detailed reason, for logs and support. |

The creatives endpoints wrap every response, success or error, in an envelope with a numeric `code`:

```json
{
  "code": 4011,
  "message": "Invalid or revoked API key",
  "server_time": 1790679032
}
```

| Field | Type | Description |
|---|---|---|
| `code` | integer | `0` on success. A four-digit error code otherwise. |
| `message` | string | `"ok"` on success, a human-readable message otherwise. |
| `data` | object | Present on success only. |
| `server_time` | integer | Server time in Unix epoch seconds. |

Each endpoint page lists its errors. For retry guidance and the full list, see [Errors](https://docs.wandura.ai/developers/concepts/errors).

## Rate limits

Each organisation has an abuse ceiling per endpoint group, counted over a fixed 60-second window: 10,800 playback requests (about 180 tickets per second), 3,000 catalog requests and 3,000 creatives requests, each on its own counter. Reaching a ceiling usually means a runaway loop. Wandura can raise any of them for your organisation on request, effective within about a minute. The limit headers below appear only on `429` responses.

When you reach it, every endpoint answers `429` with a flat body, including the creatives endpoints:

```json
{
  "error": "Too many playback requests for this account. This is an abuse ceiling, not a quota — if a normal integration reaches it, contact us and it will be raised.",
  "error_code": "rate_limited"
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds until the current window resets. |
| `X-RateLimit-Limit` | Requests allowed per window for this endpoint group. |
| `X-RateLimit-Window` | Window length in seconds. |

Wait for `Retry-After` before retrying. See [Rate limits](https://docs.wandura.ai/developers/concepts/rate-limits#limits).
