Appearance
Publishing flows
INFO
On-prem only. A project that has never been changed is on Manual review, the default.
Each project is set to one of three flows by its own team, in the console. There is no API to read or change it — but every listing and every publish response tells you which one you are on, in flow_mode.
The flow decides what your POST actually does.
| Flow | A publish through the API | Who may publish |
|---|---|---|
review_required | Goes live immediately — but only for a key whose owner holds the approver tag, because that tag is the approval authority. Any other key is refused. | project member and tagged approver |
direct_publish | Goes live immediately. | project member |
ai_assisted | A reviewer agent inspects the content during the request. Clean → live. Findings → nothing published until you respond. | project member |
review_required is the default, and every project that existed before flows shipped is on it. So nothing about an existing integration changes until a project's team deliberately changes its flow.
Build for all three
A project's team can switch flows without telling you. An integration that only handles 201 will start failing the day someone turns on AI-assisted publishing.
Handle these three outcomes on every publish and the flow setting becomes somebody else's problem:
201/200— published, or nothing to do409withactive_version_changed— someone else published first422withreview_suggestions_open— the agent has findings
That is the whole surface. Handling it once costs an afternoon.
WARNING
On Manual review projects the API is not a general publishing tool. A write key belonging to a tagged approver publishes immediately, because the tag is the approval authority — the API skips the review workflow, never the authority check. A key whose owner lacks the tag gets 403, always, and no amount of retrying changes it. If your UI is meant to put content live without a human step, ask for direct_publish or ai_assisted.
AI-assisted: the review handshake
Only applies on ai_assisted. On the other two flows nothing in this section can happen.
When you publish, a reviewer agent compares your content against the version it would replace — during the request. Two outcomes.
Clean → published, one call
Nothing to do. You get 201 as usual, with review_status: "clean".
Findings → nothing published, and you decide
Call 1 — an ordinary publish:
bash
curl -s -X POST "$IM_URL/prompts/active-prompt-version?project_id=$PID&prompt_name=$NAME" \
-H "X-API-Key: $IM_KEY" -H "Content-Type: application/json" \
-d '{
"content": { "kind": "instruction", "text": "Summarise the document." },
"message": "shorter instruction",
"expected_active_sha": "c8aceef013a8"
}'422 — nothing was published:
json
{
"detail": {
"code": "review_suggestions_open",
"message": "The reviewer agent has open suggestions on this content.",
"review_id": "rv_8f21c4",
"suggestions": [
{
"rule_id": "PM-REV6",
"severity": "HIGH",
"confidence": 0.86,
"anchor": "text",
"title": "Output length constraint was removed",
"explanation": "The version you are replacing capped the summary at 200 words. This one has no limit, so downstream consumers expecting a short summary may break.",
"suggestion": "Summarise the document in no more than 200 words."
}
]
}
}Now pick one of two paths.
Path A — fix it. Apply what you agree with and publish again. The agent re-runs on the new content and, if it is happy, returns 201. This is the normal outcome and the one to design for.
Path B — publish anyway. Send the same content plus the review_id you were given:
bash
-d '{
"content": { "kind": "instruction", "text": "Summarise the document." },
"message": "shorter instruction",
"expected_active_sha": "c8aceef013a8",
"acknowledge_review": "rv_8f21c4"
}'201, and the override is recorded on the version with the findings it waived, so the team can see what was overridden and by whom.
Why an id and not a boolean
A review_id belongs to one review of one exact piece of content. That matters twice.
You cannot switch the gate off by accident. A boolean like acknowledge: true would get hardcoded once and then silently wave through every publish forever — including findings nobody read. A review_id cannot be hardcoded: send it with different content and the server sees it no longer matches the current review, and answers with a fresh 422 and the new findings.
It is the same idea as expected_active_sha, which you are already sending: you echo back the exact thing you looked at, which proves you looked at it.
And it costs nothing in latency. On the second call your content is unchanged, so the server reuses the review it already ran rather than calling the model again — one model call per publish, not two.
Rules to hold on to
- Omitting
acknowledge_reviewalways means422. Doing nothing keeps you protected; only an explicit, matching id publishes over findings. - A stale
acknowledge_reviewis not an error you retry. It means the content or the review moved. Read the newsuggestionsand decide again. - Findings are advice, not a verdict.
PM-REV1…PM-REV6cover ambiguity, missing constraints, injection-prone interpolation, contradictions, missing output contracts, and guardrails a change dropped. A model can be wrong; that is exactly why Path B exists. forcedoes not waive a finding. It waives a concurrency conflict. Two different risks, two different fields — so your logs say which one somebody took.
Two failure modes to design for
| Status | code | What it means |
|---|---|---|
409 | llm_not_configured | The project is ai_assisted but its organisation has no AI provider connected. A configuration problem on the Instructions Manager side — surface it to an operator, do not retry. |
503 | review_unavailable | The review itself could not run — the model was down, timed out, or answered unreadably. Nothing was published. This one is worth retrying with backoff. |
DANGER
The gate fails closed. A review that could not run is never treated as a review that found nothing, because that would silently publish unreviewed content. That is why 503 exists rather than a quiet success.
Flows apply to skills too
Everything on this page governs POST /skills/active-version identically. The flow is a property of the project, not of prompts — so a project on ai_assisted reviews its skill publishes as well, with the same 422 → acknowledge_review → 201 handshake and the same review_id.
One skill-only addition: skill content also passes a security scan, which is separate from the AI reviewer and is not cleared by acknowledge_review. See the security scan.