How to Add an Approval Workflow for Automated Image Generation
Build a fail-closed approval gate for generated images with moderation, human review, audit logs, retries, and safe publication.
Put an explicit approval gate between image generation and publication. The reliable sequence is: validate the request, generate a draft, moderate the prompt and image, route flagged or sampled items to a reviewer, persist the decision against the exact artifact version, and allow publication only when a valid approval exists.
OpenAI documents image generation through the Image API and the Responses API. The Image API fits single-prompt generation; the Responses API fits conversational, multi-turn editing. OpenAI’s Moderation API can classify text and images, but your application still has to inspect the result before showing the output or taking a downstream action. Read the image-generation documentation and the Moderation guide for current model and parameter support.
1. Define the approval boundary
The release operation is the security boundary. Generation may create a draft, but it must not publish, send, overwrite, or trigger another consequential action until approval is present.
| State | Meaning | Allowed transition |
|---|---|---|
received |
Request accepted and permission-checked | Generate or reject |
generating |
Provider call is running | Draft or generation failure |
draft |
Artifact stored with prompt and configuration | Moderate |
moderation_review |
Automated checks are pending or produced a flag | Approve automatically, route to human, or reject |
awaiting_human |
Reviewer must decide | Approved, rejected, revision requested, or timed out |
approved |
Specific artifact version has release approval | Publish once |
rejected |
Artifact cannot be released | Optional new revision |
published |
Approved artifact was written downstream | Immutable audit record |
failed |
Generation, moderation, storage, or publication failed | Retry according to operation |
Keep separate outcomes for policy violations, uncertain classification, subjective quality rejection, and technical failure. A moderation confidence score describes classifier confidence for a label; it is not an artistic-quality score or proof that an image is safe.
2. Store the right review record
A reviewer must see the exact artifact and the action that approval would authorize. Store at least:
- A unique request ID and artifact version ID.
- The original prompt, any negative prompt, and user or tenant identity.
- Generation provider, model identifier, parameters, seed if supported, and timestamp.
- An immutable reference to the image bytes and a cryptographic digest.
- Moderation input, returned labels, confidence values, and policy version.
- Requested downstream action, such as publish, send, or overwrite.
- Reviewer identity, decision, optional note, decision timestamp, and expiry.
- Every state transition, retry, webhook, and publication result.
Approval must reference the artifact version, not only the request ID. If a revision changes the image, the previous approval is invalid.
3. Generate a draft and moderate it
The following Python example uses the OpenAI SDK shape documented for image generation. Set IMAGE_MODEL to a model currently enabled for your account, because model names and parameters can change. The moderation model shown is omni-moderation-latest, which the current moderation guide documents for text and image inputs.
import base64
import hashlib
import json
import os
import uuid
from pathlib import Path
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
IMAGE_MODEL = os.environ["IMAGE_MODEL"]
request_id = str(uuid.uuid4())
prompt = "A red fox reading a map in a quiet mountain cabin, editorial illustration"
# Generation creates a draft. Do not publish here.
result = client.images.generate(
model=IMAGE_MODEL,
prompt=prompt,
)
# SDK response shapes can vary by configured output format. Adapt this
# extraction to the format enabled in your account and persist the bytes.
item = result.data[0]
if getattr(item, "b64_json", None):
image_bytes = base64.b64decode(item.b64_json)
else:
raise RuntimeError("Configure image output retrieval for your account")
artifact_path = Path("artifacts") / f"{request_id}.png"
artifact_path.parent.mkdir(exist_ok=True)
artifact_path.write_bytes(image_bytes)
digest = hashlib.sha256(image_bytes).hexdigest()
# Moderate the prompt before routing it.
moderation = client.moderations.create(
model="omni-moderation-latest",
input=prompt,
)
moderation_result = moderation.results[0]
record = {
"request_id": request_id,
"artifact_version": 1,
"prompt": prompt,
"image_model": IMAGE_MODEL,
"artifact_path": str(artifact_path),
"sha256": digest,
"moderation": moderation_result.model_dump(),
"state": "awaiting_human" if moderation_result.flagged else "moderation_review",
}
Path("review-record.json").write_text(json.dumps(record, indent=2))
print(json.dumps(record, indent=2))
For image moderation, send the stored image as an image input using the format in the current Moderation API documentation. Keep the original bytes and the moderation response together so a later reviewer can reproduce what was checked.
4. Route automatic and human decisions
Use deterministic rules for well-defined policy checks and route ambiguity to people. A practical policy is:
- Reject when a mandatory policy rule is violated.
- Send uncertain or high-risk labels to a reviewer.
- Sample a percentage of routine, low-risk generations for quality and drift monitoring.
- Send subjective creative-quality decisions to a reviewer instead of pretending a classifier can judge taste.
- Require human review for high-stakes or ethically sensitive use cases.
Amazon documents a Rekognition plus Amazon A2I flow where confidence conditions and random sampling trigger human review. The example thresholds in that documentation are configuration examples, not universal values; tune them with your policy team and region requirements. A2I and Rekognition resources for that flow should be in the same AWS Region. See the AWS human-review documentation.
Airflow’s common AI provider includes an approval mixin that pauses an operator for human review, optionally accepts a modified output, and resumes after a decision or timeout. Its stable documentation distinguishes awaiting_input behavior in Airflow 3.3+ from deferred behavior in older versions. Check the version-specific Airflow documentation.
5. Build the reviewer screen
Show the image at a useful size beside the prompt, user context, moderation flags, intended action, and artifact version. Provide three explicit actions:
- Approve: authorize this exact digest for the stated action.
- Reject: block release and require a reason or policy category.
- Request revision: return structured feedback and create a new artifact version.
Use role-based access, protect image URLs, record reviewer identity, and prevent a reviewer from approving an artifact that changed after the page loaded. Recheck the digest and state in the release transaction.
6. Enforce fail-closed publication
Publication should perform an atomic check:
def publish_if_approved(db, artifact_id, expected_sha256, destination):
row = db.get_artifact_for_update(artifact_id)
if row is None:
raise ValueError("unknown artifact")
if row.sha256 != expected_sha256:
raise ValueError("artifact changed after review")
if row.state != "approved":
raise PermissionError("no valid approval")
if row.published_at is not None:
return "already-published"
db.mark_publishing(row.id)
try:
destination.write(row.bytes)
db.mark_published(row.id)
return "published"
except Exception:
db.mark_publish_failed(row.id)
raise
Use an idempotency key so retries cannot publish twice. If approval expires, the policy version changes, or the destination action changes, return the item to review.
7. Handle timeouts, failures, and revisions
| Condition | Safe behavior |
|---|---|
| Generation timeout | Mark generation failed, retry with a bounded policy, and never create an approval automatically. |
| Moderation outage | Keep the artifact pending; fail closed for release. |
| Reviewer timeout | Keep pending, escalate, or expire according to policy. Do not treat silence as approval. |
| Reviewer requests revision | Create a new version and rerun moderation; old approval does not carry forward. |
| Storage checksum mismatch | Quarantine the artifact and require regeneration or re-review. |
| Publication failure | Retry the write idempotently while retaining the approved state and audit trail. |
| Provider policy block | Record input/output blocking information and show a useful rejection state to the requester. |
8. Security and policy boundaries
OpenAI’s image-generation documentation describes input and output filtering and a moderation setting whose default is auto; a blocked response can identify whether input or output was blocked and may provide coarse categories. Treat provider filtering as one control inside your gate, not as your release authorization.
The Moderation API documentation says it is not designed for known or suspected child sexual abuse material. Define a dedicated escalation and handling process for that case. Also review the OpenAI Usage Policies: they restrict certain uses of a person’s likeness without consent and require human review for high-stakes decisions in areas such as employment, housing, education, finance, insurance, legal, medical, and essential government services.
9. Performance, reliability, and cost
- Persist the draft before human routing so a queue retry cannot lose the artifact.
- Use asynchronous jobs for generation and review; keep the request handler short.
- Hash once and compare the digest at review and publication.
- Cache moderation results only for the exact image bytes, prompt, policy version, and model configuration.
- Measure queue age, review latency, rejection reasons, moderation failure rate, generation retries, and publication failures.
- Set separate timeouts for generation, moderation, reviewer response, and downstream writes.
- Control costs with sampling, bounded retries, image-size limits, retention policies, and concurrency limits.
- Verify current provider pricing, model availability, and orchestration behavior before deployment; the cited documentation does not establish a universal cost or performance benchmark.
10. Or skip the browser setup
If your approval screen needs clean reference screenshots, ScreenshotNeo provides a single-call capture API at ScreenshotNeo. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response reports the result through X-Page-Verdict and X-Billed headers. It also has an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo API documentation for the full option set, including full-page capture, CSS selectors, device presets, custom CSS and JavaScript, waits, request blocking, headers, cookies, geolocation, caching, signed links, asynchronous jobs, bulk capture, and PDF output. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
11. Troubleshooting
Images are published without a review
Check that every write path calls the same approval-enforcing service and that direct storage credentials are unavailable to workers that only generate drafts.
A revised image still has the old approval
Make the artifact digest and version part of the approval key. A new image must create a new review record.
Moderation says “safe” but reviewers reject the image
Moderation is a policy classifier, not a creative-quality evaluator. Route subjective quality and brand-fit decisions to people.
The queue stalls when a reviewer is unavailable
Set an explicit timeout, escalation target, and expiration state. Never convert a timeout into approval.
Retries create duplicate publications
Use an idempotency key and a database uniqueness constraint for the destination write. Record the provider request ID where available.
Provider model or parameter errors appear after deployment
Model names and supported parameters are version-sensitive. Confirm the current provider documentation and pin compatible SDK versions.
FAQ
Can automated moderation replace human approval?
Use automation for defined checks and routing. Keep a human gate for ambiguous, subjective, high-risk, or high-stakes decisions.
Should every image be reviewed?
That depends on policy and risk. A common design reviews all flagged items and samples routine low-risk items for quality and drift.
What happens when moderation is unavailable?
Keep the artifact pending and block release until the required checks complete or an authorized human handles the exception.
Can a reviewer edit the image?
Yes, if your workflow supports revision. Treat the edited result as a new artifact version and rerun moderation.
Which API should I use for multi-turn image editing?
OpenAI documents the Responses API for conversational, multi-step image experiences and the Image API for single-prompt generation. Confirm current support before implementation.


