POST /v1/content/:containerId/approve
Flip a container's approval status to approved so it can be scheduled.
/v1/content/:containerId/approve- Auth
- Bearer
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 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 when the project's approval policy requires review. Without approval, scheduling returns APPROVAL_REQUIRED.
Path parameters
containerIdstring (uuid)requiredThe container to approve.
Body
notestringoptionalFree-text note, max 1024 chars. Stored on the container for audit.
Request
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." }'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();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()Responses
{
"id": "cnt_7d18b9a1...",
"approvalStatus": "approved",
"approvedAt": "2026-04-18T09:40:12Z",
"approvedBy": "api_key_c2037bb9..."
}{
"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 to re-create the rows.
{
"error": {
"code": "CONFLICT",
"message": "Container is already approved.",
"requestId": "req_...",
"details": { "approvalStatus": "approved" }
}
}{
"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 or publish.
Notes
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.
- 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 is
review_first_n, the gate self-disables oncefirstNcontainers have moved out ofpending(approved + rejected count). Subsequent generations land atapprovalStatus: "not_required"automatically — no further approvals needed. - Publishing consequence. Approving doesn't publish. It just removes the gate. Schedule or publish now to actually put it on a surface.
- Pending-schedule promotion. If
schedulewas called before approval, those targets were stashed on the container aspendingSchedulemetadata and returned withgateStatus: "blocked_on_approval". This call promotes them to livescheduled_postsrows atomically - thescheduledPostIdsyou already received fromschedulebecome the ids of the new rows. The response surfaces the outcome underpendingSchedulePromotion.
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. |