Skip to content

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.

FlowA publish through the APIWho may publish
review_requiredGoes 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_publishGoes live immediately.project member
ai_assistedA 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 do
  • 409 with active_version_changed — someone else published first
  • 422 with review_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"
      }'

422nothing 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_review always means 422. Doing nothing keeps you protected; only an explicit, matching id publishes over findings.
  • A stale acknowledge_review is not an error you retry. It means the content or the review moved. Read the new suggestions and decide again.
  • Findings are advice, not a verdict. PM-REV1PM-REV6 cover 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.
  • force does 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

StatuscodeWhat it means
409llm_not_configuredThe 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.
503review_unavailableThe 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 422acknowledge_review201 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.

Next

Copyright © 2026 elsai foundry.