---
title: "Request a governed autonomous publish"
method: POST
path: "/cms/posts/{id}/autonomous-publish"
tags: ["CMS"]
---

# Request a governed autonomous publish

`POST /cms/posts/{id}/autonomous-publish`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/cms/posts/{id}/autonomous-publish`
- Auth: API key in header `X-API-KEY`

Ask for a draft to be published through the autonomy gate stack, as opposed to the direct `POST /cms/posts/{id}/publish`. Requires the `write:content` scope and a user-scoped credential — an organization-only key has no actor to stamp on the audit trail and gets a 422. Nothing is published inline: the route validates organization-scoped context and emits the same `cms/autonomous-publish-requested` event the in-app agent uses, so the existing trigger runs the policy dispatcher (action allowlist, site ownership, CMS feature, autonomy level, paused state, approval circuit-breaker, consent, quality verdict, feature flag and organization allowlist, then a credit reserve) inside its own step. With autonomy or its feature flag off — the steady state — the dispatcher denies and the draft is routed to the human review queue instead; that is still a 202, never a 500 and never an ungoverned publish. FraseCMS only: a site that publishes to a connected CMS (WordPress, Webflow) is refused with 409 `destination_not_frase_cms`, because every outcome this endpoint lets you observe is a FraseCMS one. A site sitting in Trash is refused with 409 `site_deleted`. A post with no linked content row is a 422 — use the direct publish endpoint for it. Because the response is 202 and asynchronous, subscribe to the `cms.post.published` and `cms.post.review_requested` webhooks to learn the outcome. Idempotency is surface- and draft-scoped: the request is keyed on the post's content id, the `api` surface, and a digest of the draft body (returned as `draft_revision`). Re-requesting the same unchanged draft within 24 hours is deduplicated and does not run again; editing the draft produces a new revision and is evaluated afresh. An API request is never suppressed by a prior in-app agent request. A draft already waiting in the review queue is re-evaluated but denied `pending_review` — approve or reject it there.

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `signal_id` string: Opaque token, trimmed; control characters and newlines are rejected.
  - `source_brief_id` string: Opaque token, trimmed; control characters and newlines are rejected.

## Response `202`

Request accepted and routed to the autonomy gates. Not a publish confirmation.

- object
  - `data` object
    - `status` 'requested'
    - `post_id` string, uuid
    - `draft_revision` string: 16-character digest of the draft body this request was keyed on. Two requests carrying the same value inside the 24 hour window are deduplicated.
    - `message` string: Human-readable explanation of what happens next and how deduplication applies.

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `404`: Resource not found
- `409`: The site's state blocks the request. `site_deleted`: the site is in Trash, so neither a publish nor a review approval could ever land — restore it first. `destination_not_frase_cms`: the site publishes to a connected CMS, so use `POST /cms/posts/{id}/publish` or turn FraseCMS on for the site. Both are fixable by changing the site, which is why they are 409 rather than 422.
- `422`: The request cannot be governed: the credential is not user-scoped, or the post has no linked content row for the gates and quality verdict to evaluate (use the direct publish endpoint instead).
- `429`: Rate limit exceeded
- `500`: The site's CMS state could not be verified. Safe to retry.

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
