# List creatives

Returns a page of approved creatives of your organisation, each as a full data record. Use it to sync creatives into your marketing systems, incrementally with `updated_since`.

```
GET /api/delivery/v1/creatives
```

**Scope:** `creatives:export`

## Description

Returns creatives of your organisation with status `approved` or `pushed`, ordered by `updated_at` ascending and then by ID. Records never contain a media URL. Every response, success or error (except `429`), is wrapped in the `{code, message, data, server_time}` envelope. See [Creatives](https://docs.wandura.ai/developers/concepts/creatives).

## Request parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Authorization` | header | string | Yes | `Bearer <API key>` for a key with the `creatives:export` scope. |
| `updated_since` | query | integer | No | Unix epoch seconds (up to 11 digits). Returns only records whose exact update time is strictly after this second (`>`, not `>=`). |
| `cursor` | query | string | No | The `next_cursor` from the previous page. Opaque; pass it back unchanged. |
| `limit` | query | integer | No | Records per page, 1 to 100. Default `50`. |

You can combine `updated_since` and `cursor`. Keep the same `updated_since` on every page of one sync. Because `updated_at` in each record is rounded down to the second, passing the largest `updated_at` you received usually returns the last records again; overwrite them by `creative_id`.

## Request example

```bash
curl -s "https://www.wandura.ai/api/delivery/v1/creatives?updated_since=1790600000&limit=2" \
  -H "Authorization: Bearer $WANDURA_API_KEY"
```

## Response

`200 OK`

| Field | Type | Description |
|---|---|---|
| `code` | integer | `0`. |
| `message` | string | `"ok"`. |
| `data.items` | array | The creative records on this page. See [The creative record](https://docs.wandura.ai/developers/reference/creatives-list#the-creative-record). |
| `data.next_cursor` | string or null | Pass it as `cursor` to get the next page. `null` on the last page. |
| `server_time` | integer | Server time in Unix epoch seconds. |

### The creative record

The same record is returned by [Get a creative](https://docs.wandura.ai/developers/reference/creatives-get).

| Field | Type | Description |
|---|---|---|
| `schema_version` | integer | Record schema version. Currently `1`. |
| `creative_id` | string (UUID) | Stable creative ID. |
| `creative_code` | string or null | Immutable human-readable code. Treat it as opaque. |
| `name` | string | Creative name. |
| `status` | string | `approved`, or `pushed` once delivered. |
| `created_at` | integer | Creation time, Unix seconds. |
| `approved_at` | integer or null | Approval time, Unix seconds. |
| `updated_at` | integer | Last update, Unix seconds, rounded down. |
| `org.slug` | string | Your organisation's slug. |
| `film` | object or null | The film the creative is cut from. |
| `film.code` | string or null | Film code. |
| `film.title` | string or null | Film title. |
| `film.language` | string or null | Film language. |
| `film.genres` | array of strings | Film genres. Can be empty. |
| `episodes` | array of integers | Episode numbers used, without duplicates. |
| `placement` | string or null | `reels_9x16`, `feed_4x5` or `square_1x1`. |
| `aspect` | string or null | `9:16`, `4:5` or `1:1`, matching `placement`. |
| `duration_ms` | integer | Total length of the film clips used, in milliseconds. |
| `scenes` | array | The film clips used, in order. |
| `scenes[].episode_no` | integer or null | Episode the clip comes from. |
| `scenes[].start_ms` | integer | Clip start in the episode, milliseconds. |
| `scenes[].end_ms` | integer | Clip end in the episode, milliseconds. |
| `scenes[].duration_ms` | integer | Clip length, milliseconds. |
| `scenes[].action` | string or null | Action label of the scene, for example `reveal`, `argue`, `slap`, `kiss`, `embrace`, `cry`, `kneel`, `walk_away` or `violence`. `null` when the scene has no notable action. New labels may be added. |
| `scenes[].line` | string or null | The spoken line or standout line of the scene. |
| `hook` | object or null | `action` and `line` of the first scene. `null` when there are no scenes. |
| `formula` | object or null | `{ "name": ... }` of the formula the creative was built with. |
| `rights.ends_at` | integer or null | Last day of your export rights for the film, as Unix seconds at 00:00 UTC of that day. Your rights include that whole day: stop using the creative after that UTC day ends, that is at `ends_at + 86400`. `null` when Wandura has no end date on record for your export rights. |
| `lineage.parent_creative_id` | string (UUID) or null | The creative this one was derived from. |
| `final.ready` | boolean | Whether the final render exists. A creative in status `pushed` has one. |
| `final.target` | object | Target render spec: `width`, `height`, `fps`, `codec`. Today it is the same for every creative (1080 × 1920, 30 fps, `h264`) and does not follow `placement`, so do not derive the frame size from it. |

`scenes` lists only clips taken from film episodes. Other elements of the creative are not listed, so `duration_ms` can be shorter than the final video.

## Response example

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "items": [
      {
        "schema_version": 1,
        "creative_id": "5e1b7c3a-2d4f-4a6b-9c8d-0e1f2a3b4c5d",
        "creative_code": "ACV001-FILM0001-EP1-2-F1-TEAM",
        "name": "Heiress returns - wedding hook",
        "status": "approved",
        "created_at": 1790500000,
        "approved_at": 1790590000,
        "updated_at": 1790610000,
        "org": { "slug": "your-org" },
        "film": { "code": "FILM0001", "title": "The Heiress Returns", "language": "en", "genres": ["romance", "revenge"] },
        "episodes": [1, 2],
        "placement": "reels_9x16",
        "aspect": "9:16",
        "duration_ms": 21500,
        "scenes": [
          { "episode_no": 1, "start_ms": 12000, "end_ms": 19500, "duration_ms": 7500, "action": "reveal", "line": "You threw me out. Now watch me take it all back." },
          { "episode_no": 2, "start_ms": 40000, "end_ms": 54000, "duration_ms": 14000, "action": null, "line": null }
        ],
        "hook": { "action": "reveal", "line": "You threw me out. Now watch me take it all back." },
        "formula": { "name": "Cliffhanger opener" },
        "rights": { "ends_at": 1798675200 },
        "lineage": { "parent_creative_id": null },
        "final": { "ready": true, "target": { "width": 1080, "height": 1920, "fps": 30, "codec": "h264" } }
      }
    ],
    "next_cursor": "eyJwIjoiZXhhbXBsZS1jdXJzb3IifQ"
  },
  "server_time": 1790679032
}
```

## Errors

| Status | `code` | When |
|---|---|---|
| 401 | 4010 | The `Authorization: Bearer` header is missing. |
| 401 | 4011 | The key is unknown or revoked. |
| 403 | 4030 | The key lacks the `creatives:export` scope. |
| 403 | 4031 | Your organisation's access is suspended. |
| 422 | 4220 | `limit` is not an integer between 1 and 100. |
| 422 | 4221 | `updated_since` is not epoch seconds. |
| 422 | 4222 | `cursor` is not valid. |
| 429 | none | Rate limit reached. Flat body with `error_code` `rate_limited`, not the envelope. |
| 503 | 5030 | The feed is temporarily unavailable. Retry with backoff. |
| 503 | 5031 | Wandura could not verify the API key in time. The key is not wrong: retry with backoff. |

See [Errors](https://docs.wandura.ai/developers/concepts/errors#creatives-errors) for what to do in each case.
