---
title: "Accept a suggestion application and re-publish"
method: POST
path: "/optimizations/{id}/republish"
tags: ["Optimizations"]
---

# Accept a suggestion application and re-publish

`POST /optimizations/{id}/republish`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/optimizations/{id}/republish`
- Auth: API key in header `X-API-KEY`

Accept a reviewable suggestion application for an optimization and push the updated content back to the CMS destination it was originally published to. The remote post is UPDATED in place — never re-created — so the existing URL and external ID are preserved. Requires the `write:content` scope and a plan with content optimization.

Both `content_id` and `application_id` must be supplied and must line up with the optimization named in the path: the optimization's content must equal `content_id` (400 `invalid_request` otherwise), and the application must belong to the same organization, optimization and content (404 otherwise). The CMS destination and remote post id are read off the content row — you cannot supply them.

The application must be in `reviewable` state. An `accepted` or `reverted` application is 409 `already_terminal`; any other state is 409 `invalid_state`. On success the application is moved to `accepted`, and a content row sitting in `review` is moved to `published`.

This is an unattended caller, so drift is never forced: if the post changed in the CMS since Frase last published it, the request is refused with 409 `REMOTE_CONTENT_CHANGED` and must be resolved in the Frase app. Content rule violations and Trust & Safety blocks refuse with 422. A CMS integration that needs reconnecting refuses with 401 code `UNAUTHORIZED` (data-wrapped) - fix the connection in the Frase app. Rate limits apply on two axes — a per-user hourly generate budget (30/hour) checked before any work, and a per-organization re-publish limiter (5/minute) inside the publish path; both answer 429 with code `RATE_LIMITED`.

Status codes are chosen so that only genuinely retryable failures land in the 5xx range: 409, 410 and 422 refusals never self-clear and must not be retried. Every failure body except the 400 parameter errors, the 404s, and the plan-gate 403 carries a machine-readable `code` alongside `error`.

CORS preflight is supported (OPTIONS returns 204; allowed methods POST, OPTIONS; allowed headers Content-Type, X-API-KEY, token).

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `content_id` string, uuid, required: Required. Must be the content the optimization belongs to.
  - `application_id` string, uuid, required: Required. The suggestion application to accept. Must belong to this optimization and content, and be in `reviewable` state.

## Response `200`

Re-published. The application is now `accepted`, and content that was in `review` is now `published`.

- object
  - `data` object
    - `destination` 'frase_cms' | 'wordpress' | 'webflow' | 'sanity' | 'wix': The CMS the post was updated on, resolved from the content row.
    - `url` string, nullable: Live URL of the updated post, when the adapter reports one.
    - `external_id` string, nullable: The remote post's id in the destination CMS, when the adapter reports one.
    - `live_publish_failed` boolean: Webflow only, present only when true. The CMS item was updated but Webflow refused the site-level publish, so the live page still serves the OLD body and `url` is null. The write itself succeeded, which is why this is a 200 and not an error. See `live_publish_message`.
    - `live_publish_message` string: Present alongside `live_publish_failed`: what still has to happen for the update to go live, including Webflow's own sentence where it gave a usable one.

## Other responses

- `400`: Bad request. A missing or non-UUID `id`, `content_id` or `application_id` returns the standard validation envelope. Two request-shaped refusals instead return the data-wrapped shape `{"data": {"error": "...", "code": "..."}}`: `invalid_request` (the optimization is not for the supplied `content_id`) and `NO_EXTERNAL_POST_ID` (the content has no resolvable CMS destination or remote post id, so there is nothing to update).
- `401`: Two body shapes share this status: the auth layer's structured Error envelope (bad or missing API key), and — AFTER successful key auth — the handler's data-wrapped shape with code `UNAUTHORIZED` when the CMS integration itself needs reconnecting (e.g. a disconnected WordPress connection; no live integration record). A generated client must not assume the structured shape.
- `403`: Either the plan gate or the credential. The plan gate (org lacks content optimization) answers with the data-wrapped shape `{"data": {"error": "Content optimization requires <plan> plan or higher."}}`. Credential refusals (missing `write:content` scope, IP not allowlisted, expired trial) answer with the standard error envelope.
- `404`: The optimization, the content, or the suggestion application was not found in your organization (or the content was trashed). All four not-found cases share this one envelope.
- `409`: A conflict that will not clear on retry. `code` is one of: `already_terminal` (application already accepted or reverted), `invalid_state` (application is not `reviewable`), `REMOTE_CONTENT_CHANGED` (the post was edited in the CMS since Frase last published it — resolve it in the Frase app; an API key can never force past this), `site_deleted` (the site was moved to Trash after this content was linked to it), or `REVIEW_REQUIRED` (the destination requires approval before this can go out again).
- `410`: `REMOTE_POST_MISSING` — the remote post existed and has since been deleted in the CMS, so there is nothing left to update. Non-retryable.
- `422`: A deterministic content or configuration refusal — retrying the same content will always fail. `code` is one of: `rules_blocked` (the content fails one or more rule sets), `content_safety_blocked` (Trust & Safety refused the body being pushed), or `UNKNOWN_CONTENT_TYPE` (the content's type is not publishable to this destination).
- `429`: Rate limited. The route's own limiters answer with the data-wrapped shape `{"data": {"error": "...", "code": "RATE_LIMITED"}}` and carry no `Retry-After` header — the retry delay is stated in the message. Two limiters can produce it: a per-user generate budget of 30 per hour, checked before any work, and a per-organization re-publish limiter of 5 per minute inside the publish path. API-key burst, sustained and monthly limits answer earlier with the standard error envelope and a `Retry-After` header.
- `500`: `UNKNOWN` — an unclassified failure. Safe to retry.
- `502`: A genuine gateway failure, worth retrying. `UPDATE_FAILED` - the CMS rejected or could not complete the update (also covers an unsupported provider or a broken connection).
- `504`: `TIMEOUT` — the CMS did not respond in time. Retryable.

---

[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)
