Subchapter 10.94
references/seo/manage-content-plan.mdMarkdown13 KBView on GitHub
A content plan is a set of suggested blog post briefs, including titles,
keywords, and the existing site pages they support. A content plan flow is
the asynchronous job that generates those briefs. Its contentPlanFlowId is
a flow UUID, distinct from the site’s ID, and its reports progress.
Generation creates briefs, not published posts.
statusKEYWORD_RESEARCH means keyword research is complete and the job is waiting
for the Create Content Plan request to generate the briefs. This request
releases the intentional pause; polling alone does not advance it.
Use the selected site’s authorization context. Trigger and Create Content Plan are writes requiring Manage SEO Settings; execute them when the user has requested generation or explicitly confirmed it.
An existing flow is a job already started by a previous trigger, including one discussed earlier in the conversation. To finish it or read its results:
contentPlanFlowId in the conversation or a previous
trigger/status response. A site ID is not a flow ID, even though both are
UUIDs. If the ID is missing, explain the intentional pause when the user
reports KEYWORD_RESEARCH, ask for the flow ID, and end the turn without an
API call. Never submit a placeholder. Do not offer a new flow or a different
site as an alternative to recovering the ID.GET https://www.wixapis.com/promote/seo/v1/content-plan-flows/{contentPlanFlowId}.
Read contentPlanFlow.status; see the response and status table in
Check the flow status.SUCCESS, go directly to Read the briefs.
At KEYWORD_RESEARCH, when completion is requested, call
Create Content Plan once with this flow ID.
For an earlier in-progress status, continue checking this same flow until
it reaches the pause. If already at CONTENT_PLAN, continue to step 4
without calling Create Content Plan again. For a terminal or unmet-requirement
status, follow the status table and stop.When the user requests a new plan, follow these steps in order. The request and response examples for each step are in API steps.
KEYWORD_RESEARCH.Only Trigger and Create Content Plan write data in the generation path. Do not
change the site’s business profile, name, description, categories, or publication
state to accelerate it. Those are separate tasks requiring real user data and
authorization. CREATED can mean queued work, not missing setup.
One API execution makes one HTTP request and returns. The sequence below
is a series of separate calls, with a decision after each response. It is not
one code block containing the entire workflow. Never wrap API calls in a
for/while loop, a timer, or a function that polls until a target status.
Retain each response’s flow ID before the next call. Wait between status checks using the client’s supported waiting capability, outside the API execution; do not assume timers exist inside that execution or busy-wait there.
Keep checking while work progresses. If waiting cannot continue, report the flow ID and last observed status as incomplete; do not claim success or merely promise to finish later. Identify trigger and release as writes if asked whether an execution changes data.
POST https://www.wixapis.com/promote/seo/v1/content-plan-flows/trigger
{}Response:
{ "contentPlanFlowId": "<flow-uuid>" }<flow-uuid> and other angle-bracket values in these examples are placeholders;
substitute actual returned values before making requests. Return this response
and end this execution here. Save the ID before making any status request.
Do not append step 2 to the trigger script.
See Trigger Content Plan Generation Flow (opens in a new tab).
GET https://www.wixapis.com/promote/seo/v1/content-plan-flows/{contentPlanFlowId}Execute this GET once and return its response. This execution contains no
for/while loop and no timer. Repeat it as a separate call when another
status check is needed. Keep the response compact: flow ID and status suffice.
Example response, showing the public flow fields (optional fields may be absent):
{
"contentPlanFlow": {
"id": "<flow-uuid>",
"createdAt": "2026-09-08T10:00:00.000Z",
"updatedAt": "2026-09-08T10:01:00.000Z",
"status": "KEYWORD_RESEARCH",
"origin": "AGENT",
"summaryId": "<summary-uuid>",
"keywordResearchId": "<keyword-research-uuid>"
}
}Read contentPlanFlow.status, not a top-level status. If it is missing,
inspect the response instead of silently looping. Always use this generation’s
flow ID; if it stays CREATED, report the ID and observed status without
inventing missing business prerequisites.
See Get Content Plan Flow (opens in a new tab).
contentPlanFlow.status is a string enum. Status checks may skip intermediate
states; decide from the returned value rather than requiring every transition.
Typical status progression: CREATED → SITE_ANALYSIS → KEYWORD_RESEARCH
→ call Create Content Plan → CONTENT_PLAN → SUCCESS.
| Status | Meaning and next action |
|---|---|
CREATED | Queued or starting. Check the same flow again; do not change site settings. |
SITE_ANALYSIS | Analyzing site pages. Continue separate checks. |
SITE_SUMMARY | Summarizing existing content. Continue separate checks. |
KEYWORD_RESEARCH | Waiting for Create Content Plan. Release once when generation is requested. |
CONTENT_PLAN | Generating briefs. Continue separate checks; do not release again. |
SUCCESS | Ready. Read candidates in step 5. |
PENDING_REQUIREMENTS | Missing business information. Stop polling and report the actual unmet requirement from evidence. Do not invent or update business data, or repeatedly trigger replacements. |
FAIL | Generation failed. Report the flow ID and failure; do not silently start a replacement. |
CANCELED | Canceled and cannot be resumed. Report it and stop. |
UNKNOWN | No usable status. Inspect the response and report uncertainty instead of guessing progress. |
Check every few seconds using separate calls. Completion time varies.
POST https://www.wixapis.com/promote/seo/v1/create-content-plan
{ "contentPlanFlowId": "<flow-uuid>" }Successful response:
{
"success": true,
"contentPlanFlowId": "<flow-uuid>"
}Failure response fields (the diagnostic text comes from the API):
{
"success": false,
"message": "<reason returned by the API>"
}The response fields are success (boolean), message (failure reason, only
when success is false), and contentPlanFlowId (flow UUID when returned).
Check success as well as the HTTP status. If false, report message and stop;
a successful HTTP response alone is not a completed plan.
On success, retain the returned contentPlanFlowId for the next status check
and candidate read. This response is not the list of briefs: continue to steps
4 and 5. Do not call release again to retrieve results; on an already successful
flow it regenerates a plan under a new flow ID.
See Create Content Plan (opens in a new tab).
Same single-GET execution and nested response as step 2, using the release
response’s flow ID and returning after each check. Typical status progression:
CONTENT_PLAN → SUCCESS. Read candidates in a subsequent execution after
observing SUCCESS.
GET https://www.wixapis.com/promote/seo/v1/content-plan-flows/{contentPlanFlowId}/blog-post-candidatesExample response showing the fields needed to display one topic:
{
"blogPostCandidates": [
{
"id": "<candidate-id>",
"briefData": {
"h1Title": "How to Care for Handmade Ceramic Mugs",
"keyword": "ceramic mug care",
"mainKeyword": "handmade ceramic mugs",
"pageUrl": "https://www.example.com/mugs"
}
}
],
"pagingMetadata": { "count": 1, "cursors": {}, "hasNext": false }
}This is illustrative data, not the user’s results. Candidates can contain
additional fields; see the linked reference for the full contract. Omitting
paging returns all candidates in a single response. Each candidate’s brief
fields are nested under briefData, not at the candidate’s top level. Map them
directly:
const topics = response.blogPostCandidates.map(candidate => ({
id: candidate.id,
title: candidate.briefData?.h1Title,
keyword: candidate.briefData?.keyword,
mainKeyword: candidate.briefData?.mainKeyword,
supportingPageUrl: candidate.briefData?.pageUrl
}));pageUrl identifies the existing site page the proposed post supports; it is
not the URL of a newly published blog post. Generation creates briefs, not
published posts. Do not read candidate.title, candidate.keyword, or
candidate.pageUrl, or infer missing data from those nonexistent top-level
fields. If a nested field is absent, report it as unavailable and inspect the
raw candidate before making another request. Report the actual returned
titles and available keywords/supporting page URLs. Do not invent briefs or
claim completion from the release response.
See List Blog Post Candidates (opens in a new tab).
Start with the flow ID, observed SUCCESS status, and returned candidate count.
Use a compact table with one row per topic: suggested title, target keyword,
main keyword, and supporting page URL. Include the actual returned URL as a
link; do not merely say that each brief contains a URL. Avoid repeating SEO
titles and descriptions unless requested. If the answer must be shortened,
label the displayed subset and total explicitly instead of claiming to show all
topics. These are AI-generated suggestions; do not promise rankings or traffic.
Assess the returned topics before recommending them: if they are repetitive,
mostly restate the site name, or lack a clear connection to the site’s business,
say so plainly. Successful generation does not establish editorial quality.
Still show the actual results; do not silently replace weak titles with invented
ones or call them optimized without evidence. Explain what business context
would help assess or refine them, without modifying the site’s settings.
After step 2, before or after step 3, read the keywords:
GET https://www.wixapis.com/promote/seo/v1/content-plan-keyword-research-itemsEdit one keyword (field-masked, only keyword and main_keyword writable):
PATCH https://www.wixapis.com/promote/seo/v1/keyword-research-items/{itemId}
{
"keywordResearchId": "...",
"item": { "id": "...", "keyword": "new keyword" },
"fieldMask": "keyword"
}Copy-on-write: the response may carry a different keywordResearchId.
Always use the one from the response for the next write. Edits are not
durable across generations.
SUCCESS.PENDING_REQUIREMENTS.