# PATCH /v1/content/:containerId (/docs/api/reference/content/patch-container)



<Endpoint method="PATCH" path="/v1/content/:containerId" scope="content:write">
  Updates `caption` on a content item with
  `creativeType: "uploaded"`. Returns the full updated item. This is the fix
  path for a typo'd caption — including after a
  [finalize](/docs/api/reference/content/finalize-upload), whose retry body
  is ignored.
</Endpoint>

Restricted to uploaded content on purpose: generated content derives its hook from generation inputs, so a column write there wouldn't round-trip on GET. To change generated content, generate again with a fresh `hook`.

## Path parameters [#path-parameters]

<Parameters
  rows="[
  { name: 'containerId', type: 'string (cnt_uuid)', required: true, description: 'The uploaded content item to correct.' },
]"
/>

## Body [#body]

<Parameters
  title="Body"
  rows="[
  { name: 'caption', type: 'string', required: true, description: 'New caption, max 2,200 characters. May be empty — but must be present.' },
]"
/>

## Request [#request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```sh title="terminal"
    curl -X PATCH https://api.layers.com/v1/content/cnt_a3f8c2d1-7b4e-4f0a-9c6d-2e1b5a8f3c70 \
      -H "Authorization: Bearer $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{ "caption": "The roast that started it all — now in stores." }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="patch-content.ts"
    const res = await fetch(
      `https://api.layers.com/v1/content/${containerId}`,
      {
        method: 'PATCH',
        headers: {
          Authorization: `Bearer ${process.env.LAYERS_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': crypto.randomUUID(),
        },
        body: JSON.stringify({
          caption: 'The roast that started it all — now in stores.',
        }),
      },
    );
    const item = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```py title="patch_content.py"
    import os, uuid, httpx

    r = httpx.patch(
        f"https://api.layers.com/v1/content/{container_id}",
        headers={
            "Authorization": f"Bearer {os.environ['LAYERS_API_KEY']}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={"caption": "The roast that started it all — now in stores."},
    )
    item = r.json()
    ```
  </Tab>
</Tabs>

## Responses [#responses]

<Response status="200" description="The full updated content item — same shape as GET.">
  ```json
  {
    "id": "cnt_a3f8c2d1-7b4e-4f0a-9c6d-2e1b5a8f3c70",
    "projectId": "prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
    "status": "completed",
    "format": null,
    "hook": null,
    "influencerId": null,
    "sourceTiktokId": null,
    "mediaId": null,
    "caption": "The roast that started it all — now in stores.",
    "firstComment": null,
    "assets": [
      {
        "assetId": "upload-1",
        "kind": "video",
        "url": "https://media.layers.com/public-media/content-container/a3f8c2d1-.../media/upload-1.mp4",
        "thumbnailUrl": null,
        "width": 1080,
        "height": 1920,
        "durationMs": 31000,
        "mimeType": "video/mp4",
        "sizeBytes": 18874368
      }
    ],
    "preview": {
      "kind": "video",
      "primaryUrl": "https://media.layers.com/public-media/content-container/a3f8c2d1-.../media/upload-1.mp4",
      "thumbnailUrl": null,
      "imageUrls": [],
      "videoUrl": "https://media.layers.com/public-media/content-container/a3f8c2d1-.../media/upload-1.mp4",
      "hlsUrl": null,
      "durationMs": 31000,
      "aspectRatio": "9:16"
    },
    "approvalStatus": "not_required",
    "creativeType": "uploaded",
    "adsEnrollment": "opted_out",
    "platformFit": [
      { "platform": "tiktok", "ok": true, "issues": [] },
      { "platform": "instagram", "ok": true, "issues": [] }
    ],
    "createdAt": "2026-06-12T14:02:11Z",
    "completedAt": "2026-06-12T16:40:02Z",
    "failedAt": null,
    "lastError": null
  }
  ```
</Response>

<Response status="422" description="The item is generated content — PATCH is uploads-only.">
  ```json
  {
    "error": {
      "code": "VALIDATION",
      "message": "PATCH is available for uploaded content only. Generated content is corrected by regenerating.",
      "requestId": "req_...",
      "details": { "creativeType": "generated" }
    }
  }
  ```
</Response>

## Errors [#errors]

| Status    | Code                                  | When                                                                         |
| --------- | ------------------------------------- | ---------------------------------------------------------------------------- |
| 422       | `VALIDATION`                          | Caption missing, caption over 2,200 chars, or the item is generated content. |
| 404       | `NOT_FOUND`                           | Container not in your org.                                                   |
| 401 / 403 | `UNAUTHENTICATED` / `FORBIDDEN_SCOPE` | Standard auth and `content:write` scope rules.                               |

## Notes [#notes]

* The response is the full content item — identical in shape to [`GET /v1/content/:containerId`](/docs/api/reference/content/get-container), including `assets` and `preview`. There is no separate `updatedAt` field: the edit bumps the row, which surfaces as a refreshed `completedAt`.
* The caption you set here is what publishes — byte-for-byte, no AI edits. Posts already published keep the caption they went out with; the PATCH affects future scheduling.
* Media is immutable. There is no PATCH for the file itself — upload a new item if the media is wrong.

## See also [#see-also]

* [Read the container](/docs/api/reference/content/get-container)
* [Finalize a direct upload](/docs/api/reference/content/finalize-upload)
* [Upload finished content (guide)](/docs/api/guides/upload-finished-content)
