# Content items (/docs/api/concepts/content-items)



A content item — or, to use the field name, a **content container** — is one generated creative. It holds the rendered media, the captions, the hook, the format, the influencer that voiced it, and the approval state. When we talk about "a piece of content" in this API, we mean a container.

Generation is async. You `POST` a brief, you get back a job envelope with a `jobId` and a `containerId`, and you poll either the [job envelope](/docs/api/concepts/jobs) for progress or the container for the final shape.

## Lifecycle [#lifecycle]

Container status values (from the `ContainerStatus` enum):

* `queued` — job accepted, not yet running.
* `generating` — generation job is running. Media assets are empty until complete.
* `completed` — generation finished. Media and captions are populated. If the project's [approval policy](/docs/api/concepts/approval) requires review, the container's `approvalStatus` will be `pending`.
* `failed` — generation failed. Check `lastError` on the container.
* `canceled` — container was canceled (by explicit cancel or project archive).

Approval status is independent of container status — it lives on `approvalStatus` and takes values `not_required`, `pending`, `approved`, or `rejected`.

## Creating one [#creating-one]

```http
POST /v1/projects/:projectId/content
Content-Type: application/json
X-Api-Key: $LAYERS_API_KEY
Idempotency-Key: 7c2f1a3e-0b4c-4a11-9f7e-33c0a2c1bd55

{
  "format": "auto",
  "brief": {
    "hook": "The calendar app that finally respects your evenings",
    "targetPlatforms": ["tiktok", "instagram"],
    "influencerId": "inf_01HX9Y6K7EJ4T2ABCDEF",
    "themeTags": ["launch"],
    "referenceMediaIds": ["med_01HX9Y6K7EJ4T2ABCDEF"],
    "language": "en-US"
  }
}
→ 202
{
  "jobId": "job_01HX9Y6K7EJ4T2ABCDEF01234",
  "kind": "content_generate",
  "status": "running",
  "stage": "queued",
  "projectId": "254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
  "containerId": "cnt_01HX9Y6K7EJ4T2ABCDEF",
  "locationUrl": "/v1/jobs/job_01HX9Y6K7EJ4T2ABCDEF01234",
  "startedAt": "2026-04-18T12:04:11.000Z"
}
```

<Callout>
  Pair `Idempotency-Key` with every generate call. A retry inside the 24-hour window replays the original `202`; a conflicting body returns `409 IDEMPOTENCY_CONFLICT` rather than billing you for a duplicate job.
</Callout>

### Formats [#formats]

| `format`          | When to use                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| `video_remix`     | Edited video cut from your reference media + generated overlays.             |
| `slideshow_remix` | Multi-image slideshow with typographic hooks.                                |
| `ugc_remix`       | UGC-style influencer video, voiced by the selected persona.                  |
| `auto`            | Server picks based on reference media and brand signals. The safest default. |

## Reading a container [#reading-a-container]

```http
GET /v1/content/:containerId
→ 200
{
  "id": "cnt_01HX9Y6K7EJ4T2ABCDEF",
  "projectId": "254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
  "status": "completed",
  "approvalStatus": "approved",
  "format": "video_remix",
  "influencerId": "inf_01HX9Y6K7EJ4T2ABCDEF",
  "brief": { "hook": "The calendar app that finally...", "targetPlatforms": ["tiktok"] },
  "assets": [
    {
      "id": "med_01HX9Y6K7EJ4T2ABCDEF",
      "kind": "video",
      "url": "https://media.layers.com/...",
      "width": 1080,
      "height": 1920,
      "durationMs": 27500
    }
  ],
  "captions": [
    { "platform": "tiktok", "text": "..." }
  ],
  "lastError": null,
  "createdAt": "2026-04-18T12:04:11.000Z",
  "updatedAt": "2026-04-18T12:07:33.000Z"
}
```

Media URLs on `assets[].url` are long-lived permanent URLs. If you need a short-lived signed URL (your infra wants to re-host), use `GET /v1/content/:id/assets/:assetId`.

## Regenerating, cloning, rejecting [#regenerating-cloning-rejecting]

* `POST /v1/content/:id/regenerate` — remix the same container. Takes an optional `patch` that overrides brief fields. Returns `202` with a new `jobId`; the container id is preserved. Good for "same brief, different render."
* `POST /v1/content/:id/clone-from-post` — fork or reimagine an existing platform post (yours or a top performer). Produces a new container with a new id.
* `POST /v1/content/:id/reject` — reject through the [approval gate](/docs/api/concepts/approval). Optionally kicks off a regeneration atomically.

## Listing [#listing]

```http
GET /v1/projects/:projectId/content?status=completed&format=video_remix&limit=25
```

Filters: `status`, `format`, `cursor`, `limit`. Cursor-paginated. Pass `status=completed` when you're rendering a library view; combine with an approval query to drive your review UX.

## Progress during generation [#progress-during-generation]

For an in-flight container, both endpoints tell you the same story from different angles:

* `GET /v1/jobs/:jobId` — canonical progress. Use this by default.
* `GET /v1/content/:id/progress` — same shape, scoped to the container. Useful if you've lost the `jobId`.
