# POST /v1/content/:containerId/approve (/docs/api/reference/approval/approve-content)



<Endpoint method="POST" path="/v1/content/:containerId/approve" phase="1" />

Flips `approvalStatus` from `pending` to `approved` and stamps the reviewer. From the moment this call returns, the container is schedulable. There's no per-container counter to decrement — the [first-N gate](#first-n-gate) is a re-evaluated COUNT() of how many containers on the project have moved out of `pending`; approving (or rejecting) is what nudges that count.

This call is what unblocks [`schedule-content`](/docs/api/reference/publishing/schedule-content) when the project's approval policy requires review. Without approval, scheduling returns `APPROVAL_REQUIRED`.

## Path parameters [#path-parameters]

<Parameters
  rows="[
  { name: 'containerId', type: 'string (uuid)', required: true, description: 'The container to approve.' },
]"
/>

## Body [#body]

<Parameters
  title="Body (all optional)"
  rows="[
  { name: 'note', type: 'string', description: 'Free-text note, max 1024 chars. Stored on the container for audit.' },
]"
/>

## Request [#request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```sh title="terminal"
    curl -X POST https://api.layers.com/v1/content/{containerId}/approve \
      -H "Authorization: Bearer $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "note": "Looks good. Shipping tomorrow 7am." }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="approve.ts"
    const res = await fetch(
      `https://api.layers.com/v1/content/${containerId}/approve`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.LAYERS_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ note: 'Approved by agent.' }),
      },
    );
    const result = await res.json();
    ```
  </Tab>

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

    r = httpx.post(
        f"https://api.layers.com/v1/content/{container_id}/approve",
        headers={
            "Authorization": f"Bearer {os.environ[\'LAYERS_API_KEY\']}",
            "Content-Type": "application/json",
        },
        json={"note": "Approved."},
    )
    result = r.json()
    ```
  </Tab>
</Tabs>

## Responses [#responses]

<Response status="200" description="Approved. The container is ready to schedule.">
  ```json
  {
    "id": "cnt_7d18b9a1...",
    "approvalStatus": "approved",
    "approvedAt": "2026-04-18T09:40:12Z",
    "approvedBy": "api_key_c2037bb9..."
  }
  ```
</Response>

<Response status="200" description="Approved AND a previously-blocked schedule was promoted to live scheduled_posts.">
  ```json
  {
    "id": "cnt_7d18b9a1...",
    "approvalStatus": "approved",
    "approvedAt": "2026-04-18T09:40:12Z",
    "approvedBy": "api_key_c2037bb9...",
    "pendingSchedulePromotion": {
      "status": "ok",
      "scheduledPostIds": [
        "sp_b9b66cde-7c8e-43dc-a9d2-3f4e5a6b7c8d"
      ]
    }
  }
  ```

  If a `pendingSchedulePromotion` block is present with `status: "failed"`, approval still flipped - but the queued schedule did not promote (e.g. the original target social account was removed). Re-issue [`schedule`](/docs/api/reference/publishing/schedule-content) to re-create the rows.
</Response>

<Response status="409" description="Container is already approved, rejected, or not in a reviewable state.">
  ```json
  {
    "error": {
      "code": "CONFLICT",
      "message": "Container is already approved.",
      "requestId": "req_...",
      "details": { "approvalStatus": "approved" }
    }
  }
  ```
</Response>

<Response status="409" description="Container does not require approval (e.g. project policy is `auto_approve`, or the first-N gate self-disabled before this container was generated).">
  ```json
  {
    "error": {
      "code": "CONFLICT",
      "message": "Container does not require approval.",
      "requestId": "req_...",
      "details": { "approvalStatus": "not_required" }
    }
  }
  ```

  `approvalStatus: "not_required"` means the container was never gated — it is already schedulable. There is nothing to approve. Skip this call and go straight to [`schedule`](/docs/api/reference/publishing/schedule-content) or [`publish`](/docs/api/reference/publishing/publish-content).
</Response>

## Notes [#notes]

<Callout type="info">
  Approval is idempotent at the state level: if the container is already
  `approved`, we return `409 CONFLICT` rather than a silent no-op. That way
  your logic knows whether *this* call flipped the bit or a prior one did.
</Callout>

* **Who can approve.** Any org-scoped API key. Scope enforcement is forthcoming; until then any partner key with org access can call this endpoint.
* **First-N gate.** When the project's [content-review policy](/docs/api/reference/approval/policy) is `review_first_n`, the gate self-disables once `firstN` containers have moved out of `pending` (approved + rejected count). Subsequent generations land at `approvalStatus: "not_required"` automatically — no further approvals needed.
* **Publishing consequence.** Approving doesn't publish. It just removes the gate. [Schedule](/docs/api/reference/publishing/schedule-content) or [publish now](/docs/api/reference/publishing/publish-content) to actually put it on a surface.
* **Pending-schedule promotion.** If [`schedule`](/docs/api/reference/publishing/schedule-content) was called before approval, those targets were stashed on the container as `pendingSchedule` metadata and returned with `gateStatus: "blocked_on_approval"`. This call promotes them to live `scheduled_posts` rows atomically - the `scheduledPostIds` you already received from `schedule` become the ids of the new rows. The response surfaces the outcome under `pendingSchedulePromotion`.

## Errors [#errors]

| Code         | When                                                        |
| ------------ | ----------------------------------------------------------- |
| `CONFLICT`   | Container is already approved, rejected, or not reviewable. |
| `VALIDATION` | Container isn't `completed` yet.                            |
| `NOT_FOUND`  | Container id not in this org.                               |

## See also [#see-also]

* [Reject a container](/docs/api/reference/approval/reject-content)
* [Read the approval policy](/docs/api/reference/approval/policy)
* [Schedule approved content](/docs/api/reference/publishing/schedule-content)
