Creative-Agent Workflows
POST /v1/creative-agent/workflows starts one durable creative workflow shape: an input object with explicit steps. Your application provides the exact steps and tool arguments up front.
Use this endpoint when your application already knows the media operation to run and needs durable state, replayable event logs, SSE progress, resume, or cancellation.
#Workflow Endpoints
| Endpoint | Method | Use |
|---|---|---|
/v1/creative-agent/workflows |
POST |
Start a durable creative-agent workflow. |
/v1/creative-agent/workflows |
GET |
List the caller's workflows. |
/v1/creative-agent/workflows/:id |
GET |
Read a workflow snapshot. |
/v1/creative-agent/workflows/:id/events |
GET |
Read the persisted event log. |
/v1/creative-agent/workflows/:id/events/stream |
GET |
Stream workflow events over SSE. |
/v1/creative-agent/workflows/:id/resume |
POST |
Resume a recoverable workflow from persisted step state. |
/v1/creative-agent/workflows/:id/confirm-cost |
POST |
Approve or reject the cost preview for a workflow paused with cost_approval_required. |
/v1/creative-agent/workflows/:id/cancel |
POST |
Cooperatively cancel a workflow. |
/v1/creative-agent/workflows/:id/reseed |
POST |
Clone a completed or partial-failure workflow with fresh seeds. |
All workflow routes require authentication. Starting a workflow requires API-key auth.
The POST response returns after validation, plan creation, and initial persistence. Treat the 201 response as acceptance plus the first workflow snapshot, not as completion. Use the workflow snapshot, event log, or SSE stream to observe progress until the status becomes terminal.
Workflow status values are queued, running, completed, partial_failure, waiting_for_user, failed, and cancelled. SSE streams close on the terminal statuses — completed, failed, partial_failure, or cancelled. A waiting_for_user pause (including a cost-approval pause) keeps the stream open and resumes on the same connection after the pause is resolved. A partial_failure can still include artifacts from earlier completed steps.
A waiting_for_user snapshot also carries a waitingReason naming why the run paused: ask_clarifying_question, select_media_required, cost_approval_required, cost_reauthorization_required, safety_review_required, workflow_user_input_required, insufficient_credit, permission_required, or other. Only the two cost reasons are resolved through POST /:id/confirm-cost; those snapshots also set awaitingCostApproval: true, which is the field to branch on rather than matching reason strings. Both fields are derived per response, so older workflow records return them too.
#Start Request
Request fields use snake_case (shown below). Unknown field names — including camelCase variants — are rejected with a 400.
| Field or Header | Use |
|---|---|
input |
Required durable workflow input: { "title": "...", "steps": [...] }. |
input.steps |
Required array of exact creative-agent tool steps. |
workflow_id |
Alternative to input.steps. Compile and run a saved workflow template by ID. |
inputs |
Input values for a workflow_id template run. |
Idempotency-Key, X-Idempotency-Key |
Optional retry key. Reusing the same key returns the existing workflow instead of launching duplicate media jobs. |
max_estimated_capacity_units |
Optional ceiling. Rejects the workflow before persistence if the shared estimate is above this value. |
confirm_cost |
Optional explicit cost confirmation. Set false to reject positive estimated-cost workflows until your caller retries with confirmation. |
token_type |
Optional. Which token pays when tokens are billed: spark, sogni, or auto (default). External media providers still settle in Spark. |
billing_mode |
Optional. Whether an active Unlimited plan or your token balance pays: auto (default), subscription, or tokens. See Plan or tokens. |
app_source |
Optional caller identifier for analytics and support. |
safe_content_filter |
Optional Sensitive Content Filter state for this workflow. Defaults to true. Send false to generate mature-theme work on models that allow it. |
media_references |
Optional request-level media references available to workflow steps and $input_media dependency bindings. |
#Sensitive Content Filter
The filter is a per-request setting on the API and defaults to on. The Sensitive Content Filter switch in Sogni Web and Sogni Chat is stored in that browser and does not carry over to API calls — send safe_content_filter: false on each request instead.
Turning it off needs an eligible account — an active Sogni subscription, Premium Spark, or paying with SOGNI. Sogni re-checks eligibility on every render, so an ineligible account is refused at generation time even when the flag is set. Some models keep their own content policies that the flag cannot override: the GPT Image 2 and GPT Image 2.5 models, and the Seedance 2.x, HappyHorse, and standard Wan 3 video models, refuse mature-theme prompts either way. Child-safety prompt screening is a separate system with its own rules, and this flag does not control it.
A workflow started with the filter on rejects a mature-theme step with SAFETY_REJECTED and pauses on waiting_for_user with waitingReason: "safety_review_required". Resume it with the filter off rather than rebuilding the workflow — see Resume A Workflow.
#Plan or tokens
Two fields decide how a workflow is paid, and they control different things:
token_typepicks which token pays when tokens are billed: Spark or SOGNI.billing_modepicks whether an active Unlimited plan or your token balance pays.
billing_mode |
Behavior |
|---|---|
"auto" |
Default. An active Unlimited plan covers every step it can, and no tokens are spent on those steps. Everything else bills your token balance according to token_type. Without a plan, this is plain token billing. |
"subscription" |
Bill eligible steps against your Unlimited plan only. A step the plan cannot cover fails instead of billing tokens. |
"tokens" |
Always bill your token balance according to token_type, even when a plan is active. |
Any other value returns 400. External vendor models are never plan-covered and always bill Premium Spark, whatever billing_mode says. A plan does not cover a request that asks to pay in SOGNI (token_type: "sogni"): under auto that request is paid in SOGNI.
Changing token_type does not move a job off your plan: with an active plan and billing_mode left at auto, eligible steps are plan-covered whether token_type is spark or auto. To spend Premium Spark you hold while a plan is active, send billing_mode: "tokens". Jobs paid with Premium Spark get fastest-priority queue access, ahead of plan-covered jobs.
{
"input": { "title": "Priority render", "steps": [ { "...": "..." } ] },
"token_type": "spark",
"billing_mode": "tokens"
}
Resume and reseed accept billing_mode too. A reseed inherits the source run's setting unless you send a new one. On resume, a new value replaces the stored one for the rest of the run.
#Limits, errors, and retries
The API limits how many workflows one account can have active at once (10 by default) and how fast it can start new ones (300 per hour by default), and it caps active workflows platform-wide. A start or reseed that runs into one of these is refused before anything is billed:
| Status | Meaning | What to do |
|---|---|---|
409 |
You already have the maximum number of active workflows. details.activeWorkflowCount and details.activeWorkflowLimit give the numbers. |
Wait for one of your workflows to finish, or cancel one, then send the start again. Follow a running workflow with /stream or GET /:id rather than re-sending the start. This refusal does not use up your start allowance. |
429 |
You are starting workflows too fast, or the platform is at capacity. A start-rate refusal carries the wait in the Retry-After header and, in seconds, in the body's retryAfter. |
Wait at least that long before the next start; a request sent sooner is refused again. When no wait is given, back off with jitter before retrying. |
{
"status": "error",
"errorCode": 126,
"message": "Creative workflow start rate limit exceeded. Wait before starting another workflow.",
"retryAfter": 1837,
"details": { "retryAfterSeconds": 1837 }
}
Send an Idempotency-Key on every start and reseed, and reuse it when you retry a request that timed out or lost its connection. The retry returns the workflow the first request created instead of starting, and billing, another one. A workflow can carry many steps, so batching related renders into one workflow uses one active slot for all of them. For render concurrency, serverless patterns, and billing for production volume, see Production Integrations. Need higher limits? Tell us what you're building at [email protected].
#Step Shape
A workflow supports at most 12 steps per run. Each step supplies an exact hosted tool call:
{
"id": "clip",
"toolName": "animate_photo",
"arguments": {
"prompt": "Slow dolly-in on the generated keyframe.",
"videoModel": "ltx25",
"duration": 5
},
"dependsOn": [
{
"sourceStepId": "keyframe",
"sourceArtifactIndex": 0,
"targetArgument": "sourceImageIndex",
"mediaType": "image",
"transform": "image_index",
"required": true
}
]
}
Steps submitted through input.steps run one at a time, in array order. dependsOn wires an earlier step's artifact into a later step's arguments; it does not reorder execution, so list the steps in the order they should run. Concurrency exists only for template runs: a BatchStage compiles to sibling steps named <stageId>__<n>, and consecutive siblings dispatch in parallel up to 8 at a time, minus any sibling that depends on another in the same group. Persisted events stay in batch-index order either way, so SSE replay is deterministic.
Supported hosted tools are the generation and editing tools (generate_image, edit_image, upscale_image, restore_photo, apply_style, refine_result, change_angle, generate_video, animate_photo, sound_to_video, video_to_video, generate_music, generate_speech, stitch_video, orbit_video, dance_montage, extend_video, replace_video_segment, overlay_video, and add_subtitles), the media inspection tools (analyze_image, analyze_video, and extract_metadata), plus the hosted agent/control and manifest tools: create_asset_manifest, inspect_asset, label_asset, map_assets_for_model, validate_asset_references, ask_clarifying_question, and finalize_response.
For a promptless image enlargement, use an upscale_image step with sourceImageIndex and either scale: 2|3|4 or targetLongestEdge. The 8K and 16K profiles use 7680 and 15360 respectively; RTX VSR preserves aspect ratio and aligns both output edges to 8px.
generate_speech is the spoken-audio step — narration, voiceover, a line of dialogue — as distinct from generate_music, which composes songs and instrumentals. Its model picks how the speaker is chosen: "voice" (the default) reads in one of nine studio voices named by voice, optionally restyled with voiceDescription; "clone" reproduces a voice from a recording and requires voiceSourceIndex; "design" invents a speaker from a written voiceDescription and takes no recording. Only prompt — the text to speak — is required. In a durable workflow, point voiceSourceIndex at a request media reference or bind the recording through a dependency, the same as any other audio input.
#LoRA Steps
generate_image, edit_image, generate_video, and animate_photo accept an ordered LoRA stack. loras holds the ids; loraStrengths is positional against it, so loraStrengths[i] applies to loras[i]. Order matters, because adapters apply in sequence and do not commute.
Step arguments are schema-validated before anything dispatches, so these are hard 400s rather than silent corrections:
| Mistake | Result |
|---|---|
More than 8 entries in loras or loraStrengths |
Argument "loras" must contain at most 8 items |
loras: [] |
Argument "loras" must contain at least 1 item |
loraStrengths a different length than loras |
Arguments "loras" and "loraStrengths" must contain the same number of entries |
loraStrengths without loras |
Argument "loraStrengths" requires "loras" |
Omitting loraStrengths entirely is valid and is the one supported way to leave strengths unset — every LoRA then applies at 1.0. That is not "use the catalog default"; nothing downstream fills a per-LoRA default in.
LoRAs are model-gated. A stack sent to a model that cannot load it is filtered out silently instead of failing the step, so select the model in the same step:
| Step tool | Models that load LoRAs |
|---|---|
generate_image |
model: "krea-2-turbo" or "dark-beast-krea2" |
edit_image |
model: "krea-identity-edit" or "dark-beast-krea2-identity-edit"; personal LoRAs also work with "qwen" |
generate_video |
videoModel: "minimax-h3-t2v", "minimax-h3-t2v-turbo", "minimax-h3-t2v-balanced", "minimax-h3-fasth3-t2v-turbo", "minimax-h3-fasth3-t2v-turbo-2stage", "minimax-h3-r2v", "minimax-h3-r2v-turbo", "minimax-h3-r2v-balanced", "minimax-h3-r2v-2stage", or "minimax-h3-r2v-balanced-2stage" |
animate_photo |
videoModel: "minimax-h3-i2v", "minimax-h3-i2v-turbo", "minimax-h3-i2v-balanced", "minimax-h3-fasth3-i2v-turbo", "minimax-h3-fasth3-i2v-turbo-2stage", "minimax-h3-flf2v", "minimax-h3-flf2v-turbo", "minimax-h3-flf2v-balanced", "minimax-h3-fasth3-flf2v-turbo", or "minimax-h3-fasth3-flf2v-turbo-2stage" |
The fasth3 selectors are the separate FastVideo VSA four-step engine — roughly twice as fast as the LightX2V Turbo modes and fixed to Euler/simple. FastH3 has no reference-to-video mode, which is why generate_video lists a FastH3 text-to-video selector but no FastH3 R2V. For two-stage reference-to-video, use minimax-h3-r2v-2stage (Standard) or minimax-h3-r2v-balanced-2stage (Balanced).
curl https://api.sogni.ai/v1/creative-agent/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lora-portrait-001" \
-d '{
"input": {
"title": "LoRA portrait",
"steps": [
{
"id": "portrait",
"toolName": "generate_image",
"arguments": {
"prompt": "Close-up portrait, window light, 85mm",
"model": "krea-2-turbo",
"loras": ["krea2-realism", "krea2-skin-detail"],
"loraStrengths": [0.8, 0.45]
}
}
]
},
"token_type": "spark"
}'
The video equivalent pairs the stack with an H3 mode and the trigger word:
{
"id": "clip",
"toolName": "generate_video",
"arguments": {
"prompt": "r34l1sm, a barista pulls an espresso as steam catches the light",
"videoModel": "minimax-h3-t2v",
"duration": 5,
"loras": ["h3-realism-people"],
"loraStrengths": [0.9]
}
}
Krea 2 image LoRAs are bipolar sliders: each id names its positive direction, a negative strength applies the opposite, and 0 disables it. Some LoRAs also need a trigger word: h3-realism-people, published on every H3 mode, wants r34l1sm near the front of the prompt, or the render returns as ordinary H3 with no error.
Published ids, per-LoRA strength ranges, and maturity flags come from GET /v1/loras/comfy?modelId=<canonical-model-id> — the canonical worker model id rather than the tool key, for example ?modelId=krea2_turbo_fp8_scaled or ?modelId=minimax-h3-fl2va-fp8_i2v. Filtering is per-id, not all-or-nothing: an id the model cannot load is dropped while the compatible remainder still applies, and only when nothing survives are the LoRA parameters left off the render entirely. The first render with an uncached LoRA takes longer to start while the worker downloads it.
Unlimited subscribers can also pass ready personal LoRAs by their exact personal-… IDs, discovered through authenticated GET /v1/loras/personal/catalog. Personal LoRA strength must be greater than 0 and at most 1. Unlike public IDs, a personal ID sent with a model that cannot use it fails the step with 400 instead of being dropped.
The compose_workflow and compose_workflow_template planners can emit LoRA steps too, but only when the brief asks for an effect a published id covers.
#H3 Reference Audio Policy
A generate_video step on minimax-h3-r2v or minimax-h3-r2v-turbo that passes referenceVideoIndices or referenceAudioIndices must also send sourceAudioPolicy. These H3 modes generate audio jointly with the picture, so a reference clip's soundtrack has no safe implicit role — the argument has no default, and omitting it fails the start with a 400 naming the argument rather than guessing.
| Value | Use |
|---|---|
reuse_exact |
Keep the source audio itself. The correct choice for a specific, original, or trending song. |
reference_only |
Generate new audio guided by the source. Use only when the caller explicitly wants newly generated audio. |
replace |
Substitute different audio. Use only when the caller explicitly authorizes it. |
sourceAudioPolicy is rejected on any other model — including the H3 text-to-video and image-conditioned modes — with Argument "sourceAudioPolicy" is only supported by MiniMax H3 R2V models.
The two source-conditioned policies are also checked against the prompt, because the policy only takes effect if the prompt carries the matching official markers. reuse_exact requires an [... audio reuse ...] summary task, <Audio 1>: fully_copy in the retention analysis, and a non_diegetic_music field naming <Audio 1> directly. reference_only requires an [... audio reference ...] summary task. Each missing marker is its own validation error, so a step can fail on several at once.
H3 reference videos must themselves be exactly 24 fps; normalize other frame rates without changing duration or audio timing before submitting. Ref2VA remains loose visual conditioning and cannot promise edit-level beat synchronization.
#Planning From A Brief
If you have a creative brief but not an exact steps[] array, call the compose_workflow planner directly through /v1/creative-agent/tools/execute. This avoids spending an extra chat-completions round only to force a known tool call:
curl https://api.sogni.ai/v1/creative-agent/tools/execute \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "compose_workflow",
"arguments": {
"brief": "A 5-shot neon bakery teaser, 9:16, 15s.",
"max_estimated_capacity_units": 25
},
"token_type": "spark"
}'
The response includes a validated plan (matching this endpoint's input shape), an estimated_capacity_units value, and a fits_budget flag against the caller-supplied max_estimated_capacity_units. Submit the plan unchanged here for durable execution; pair the submission with an explicit Idempotency-Key (the planner itself is non-deterministic). Use /v1/chat/completions with sogni_tools: "creative-agent" when the LLM should choose tools from a natural-language request.
compose_workflow_template is the sibling planner for builder UIs: it returns a savable, parameterized template_draft (typed inputs[], stages[] referencing $inputs.<name>, optional graph layout) alongside an example plan for the inputs the planner used. Save template_draft through /v1/creative-agent/workflows/templates, then start future runs with workflow_id and inputs.
The same planner also edits an already-saved template. Pass the stored template JSON as existing_template and describe the change; the planner preserves the existing stage ids and the template id, and revises only what was asked. Use it rather than compose_workflow whenever the target is a saved workflow, and note that the result is still a draft — the caller saves it back through the templates endpoint.
See Chat Completions → Workflow Planning for the end-to-end Plan → Review → Execute example.
#Run From A Template
Template runs (workflow_id) authorize cost on start: the API compiles the template, builds a cost preview, and returns HTTP 202 with { workflow, waitingForCostApproval: true, preview } in a waiting_for_user (cost_approval_required) state — no step dispatches until you approve via POST /:id/confirm-cost. (Inline input.steps starts keep the legacy auto-dispatch path and return 201.) Send workflow_id plus an inputs object instead of input.steps:
curl https://api.sogni.ai/v1/creative-agent/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: template-run-001" \
-d '{
"workflow_id": "wf_two-shot-product-teaser_a1b2c3d4",
"inputs": {
"brief": "A translucent speaker on a glossy black table, neon rim light"
},
"token_type": "spark",
"confirm_cost": true
}'
The API rejects requests that include both workflow_id and input.steps. Compile failures return 422 with details.compileErrors and details.compileWarnings. See Workflow Templates for template CRUD, visibility, and fork behavior.
Request-level media_references seed the same media execution context used by hosted chat, so creative tool arguments can use negative indices such as sourceImageIndex: -1, sourceVideoIndex: -1, audioSourceIndex: -1, or referenceImageIndices: [-1] to reference request media. A dependency with sourceStepId: "$input_media" can bind one of those request media references into a later step as an image, video, audio URL, media index, or structured asset_ref. Dependency bindings are considered during validation, so a required media field may be omitted from arguments when a dependency supplies it.
#Generated Keyframe To Video
To generate an image and then animate it, send two explicit steps. The generated image is passed to the video step as an artifact dependency.
OpenAI GPT Image 2.5 Sunburst and Flare (model: "gpt-image-2.5-sunburst" or "gpt-image-2.5-flare"), legacy GPT Image 2 (model: "gpt-image-2"), ByteDance Seedance 2.5 (videoModel: "seedance2-5"), Seedance 2.0 (videoModel: "seedance2" or "seedance2-mini"), Alibaba HappyHorse 1.1 (videoModel: "happyhorse-1.1-t2v", "happyhorse-1.1-i2v", or "happyhorse-1.1-r2v"), Alibaba Wan 3 (videoModel: "wan3.0-video"), and Wan 3 Uncensored (videoModel: "wan3.0-spicy-video") are external media models that require credit card purchased Premium Spark. The legacy "seedance2-fast" value routes to the faster, lower-cost "seedance2-mini". Use token_type: "spark" for explicit billing; requests using auto are normalized to Spark for those media jobs.
curl https://api.sogni.ai/v1/creative-agent/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: robot-sketch-001" \
-d '{
"input": {
"title": "Generated keyframe to video",
"steps": [
{
"id": "keyframe",
"toolName": "generate_image",
"arguments": {
"prompt": "A graphite robot sketch on a drafting table",
"model": "qwen-2512",
"width": 1024,
"height": 576
}
},
{
"id": "clip",
"toolName": "animate_photo",
"arguments": {
"prompt": "The camera pushes in as the sketch comes alive",
"videoModel": "ltx25",
"duration": 5,
"width": 1024,
"height": 576
},
"dependsOn": [
{
"sourceStepId": "keyframe",
"sourceArtifactIndex": 0,
"targetArgument": "sourceImageIndex",
"mediaType": "image",
"transform": "image_index",
"required": true
}
]
}
]
},
"token_type": "spark",
"max_estimated_capacity_units": 25,
"confirm_cost": true
}'
Representative response:
{
"status": "success",
"data": {
"workflow": {
"workflowId": "wf_durable_workflow_...",
"status": "running",
"input": {},
"plan": {},
"events": [],
"artifacts": [],
"createTime": 1773353812,
"updateTime": 1773353812
}
}
}
#Request Media
Seedance hosted video steps accept HTTPS image, video, and audio reference URLs. Three Seedance tiers are selectable, by model alone — not by media quality or targetResolution:
videoModel |
Tier |
|---|---|
seedance2 |
Full Seedance 2.0. Use this tier when 4K is required. |
seedance2-mini |
Seedance 2.0 Mini, the lower-cost 720p variant for iteration. |
seedance2-5 |
Seedance 2.5, the newest generation. 480p, 720p, and 1080p. |
The Seedance 2.0 family runs at fixed 24 fps, supports 4-15 second clips, and accepts up to 9 images, 3 videos, and 3 audios, capped at 12 reference files in total. Seedance 2.5 also runs at fixed 24 fps, renders 4-30 seconds in a single call at up to 1080p, adds first-and-last-frame conditioning, and raises the reference budget to 30 images, 10 videos, and 10 audios, capped at 50 files in total. Pick seedance2-5 for current storyboard workflows, one continuous clip longer than 15 seconds, or a first-and-last-frame transition; stay on seedance2 when the request needs 4K. Every tier returns native audio unless the step sets generateAudio: false, omits negative prompts, and requires credit card purchased Premium Spark. Use Media Upload URLs when your app needs Sogni-hosted presigned URLs for local files.
Wan 3 hosted steps use one model ID for text, first/last frames, loose image/video/audio references, and audio-driven generation. generate_video accepts duration, ratio, targetResolution, generateAudio, expandPrompt, watermark, and document/web context. For an exact prompt, set skipPromptProcessing: true and expandPrompt: false; otherwise Sogni shapes the brief once and disables Alibaba's second expansion. Video inputs are loose references for a new generation, not provider-backed source-video editing or extension; use generate_video with referenceVideoIndices for best-effort conditioning or choose an editing-capable video_to_video model when source preservation matters.
Wan 3 Uncensored uses Sogni model ID wan3.0-spicy-video. It supports the same explicit 2–30 second duration, adaptive/fixed ratio, native-audio, and prompt-expansion controls, but does not accept document/web context or watermark. Its content policy is more permissive than standard Wan 3's, for creators 18 or older making lawful mature-theme work, and it requires the Sensitive Content Filter off. For both Wan 3 variants, first/last-frame anchors and loose media references are mutually exclusive.
Wan 3 renders on fixed canvases: ratio (16:9, 4:3, 1:1, 3:4, or 9:16) combined with targetResolution (480, 720, or 1080; default 1080) selects one, so ratio: "3:4" at 720 renders 720x960. ratio alone is enough; width and height are not required. When a step sends more than one shape input, explicit width and height together take precedence, then aspectRatio, then ratio. ratio: "adaptive" lets the model derive the shape from the input media.
generate_video has no first-frame field. referenceImageIndices are loose references that guide a new generation without locking any frame, so they do not guarantee identity or composition. To anchor a supplied image as the actual first frame, use an animate_photo step with sourceImageIndex (add frameRole: "both" and endImageIndex for a first-and-last-frame pair). Omit ratio or set it to "adaptive" to keep the source image's own shape.
{
"id": "wan3-first-frame",
"toolName": "animate_photo",
"arguments": {
"prompt": "She turns toward the camera and smiles, real-time natural motion.",
"videoModel": "wan3.0-spicy-video",
"sourceImageIndex": -1,
"frameRole": "start",
"ratio": "adaptive",
"duration": 5,
"targetResolution": 720,
"generateAudio": true
}
}
{
"id": "wan3",
"toolName": "generate_video",
"arguments": {
"prompt": "A presenter walks through a detailed workshop and says \"Welcome to the future.\"",
"videoModel": "wan3.0-video",
"duration": 8,
"targetResolution": 1080,
"ratio": "16:9",
"generateAudio": true,
"expandPrompt": true,
"watermark": false
}
}
curl https://api.sogni.ai/v1/creative-agent/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_references": [
{ "kind": "video", "url": "https://...presigned-download-url..." }
],
"input": {
"title": "Seedance V2V",
"steps": [
{
"id": "seedance_v2v",
"toolName": "video_to_video",
"arguments": {
"prompt": "Transform the source clip into a polished perfume commercial with glass reflections",
"videoSourceIndex": -1,
"videoModel": "seedance2-5",
"controlMode": "seedance-v2v",
"targetResolution": 1080,
"duration": 4
}
}
]
},
"token_type": "spark"
}'
#Chained Media Steps
Hosted workflows can chain GPT Image 2.5 Sunburst reference stills into Seedance 2.5 video segments by passing the image artifact URL into the video step:
curl https://api.sogni.ai/v1/creative-agent/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": {
"title": "GPT Image 2.5 scene reference to Seedance 2.5",
"steps": [
{
"id": "keyframe",
"toolName": "generate_image",
"arguments": {
"prompt": "A premium sneaker hero shot on wet asphalt with neon rim light",
"model": "gpt-image-2.5-sunburst",
"gptImageQuality": "high",
"outputFormat": "webp",
"width": 1280,
"height": 720
}
},
{
"id": "clip",
"toolName": "generate_video",
"arguments": {
"prompt": "Use @Image1 as the first frame. Slow dolly-in, rain glints, polished product ad energy.",
"videoModel": "seedance2-5",
"targetResolution": 1080,
"duration": 5,
"aspectRatio": "16:9"
},
"dependsOn": [
{
"sourceStepId": "keyframe",
"sourceArtifactIndex": 0,
"targetArgument": "referenceImageIndices",
"mediaType": "image",
"transform": "image_index"
}
]
}
]
},
"token_type": "spark"
}'
Hosted preflight validation blocks workflow steps that would generate more than 20 minutes of video content in one request, including long-video segment plans, variations, and batch fan-out. Split larger jobs into multiple workflows.
Storyboarding workflows that generate a GPT Image 2.5 Sunburst storyboard sheet should treat the sheet as production planning material, not as the final video frame. Unless the user explicitly specifies a storyboard canvas or output aspect, generated storyboard sheets default to a landscape board. Visible text is scoped to the scene or end-card where it is requested; do not assume earlier scene text should repeat on later panels or the final frame.
stitch_video joins whole clips end-to-end. For alternating or interleaved slices of existing videos, use repeated replace_video_segment steps with explicit replacementStartSeconds / replacementEndSeconds source windows.
#Sogni Agent CLI
The public Sogni Creative Agent Skill wraps durable workflows with sogni-agent --api-workflow:
sogni-agent --api-workflow \
--video-prompt "The camera slowly pushes in as the sketch comes alive" \
--duration 5 \
"A graphite robot sketch on a drafting table"
Use --workflow-input for exact JSON:
sogni-agent --api-workflow \
--workflow-input @workflow.json \
--watch-workflow
Workflow management helpers map directly to the REST routes: --list-workflows, --get-workflow <id>, --workflow-events <id>, --stream-workflow <id>, --resume-workflow <id>, and --cancel-workflow <id>. --watch-workflow streams shared human-readable progress labels for planning, approvals, repairs, tool execution, waiting states, and terminal errors. Hosted API modes require SOGNI_API_KEY. The CLI forwards media references from flags such as --ref, --ref-audio, and --ref-video as hosted API metadata; for durable workflows, prefer public HTTPS URLs or Sogni artifact URLs because the backend must retrieve non-inline media. Use the direct CLI path for private or large local media.
#Stream Events
curl https://api.sogni.ai/v1/creative-agent/workflows/wf_.../events/stream \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: text/event-stream"
The SSE stream replays known events on connect, then tails the persisted workflow record. It closes when the workflow reaches a terminal status.
EventSource cannot send the Authorization header. Use fetch with ReadableStream, or another HTTP client that can set headers, when consuming the stream from a browser.
#Resume A Workflow
curl -X POST https://api.sogni.ai/v1/creative-agent/workflows/wf_.../resume \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "token_type": "spark" }'
For recoverable queued/running workflows, resume starts a background executor from the persisted workflow plan and returns 202 with the current snapshot:
{
"status": "success",
"data": {
"workflow": { "...": "..." },
"resumed": true
}
}
The executor reacquires a workflow lease, rehydrates completed step outputs, skips steps already marked completed, and continues from the next uncompleted step. Use it for executor interruptions or stale in-progress runs; completed and cancelled workflows are not resumable, and failed or partial-failure runs should generally be inspected and relaunched with corrected input. The API can also run a recovery worker that scans stale queued/running workflows, reacquires expired leases, and resumes them with the owner's API key when available. resume does not release a cost_approval_required pause — those return 409 and must be resolved through confirm-cost (below). Resume and reseed also accept app_source (body) / X-App-Source (header) for attribution, like start.
Resume also accepts safe_content_filter, which re-records the workflow's Sensitive Content Filter state before the run continues. This is how a workflow paused on safety_review_required is released:
curl -X POST https://api.sogni.ai/v1/creative-agent/workflows/wf_.../resume \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "safe_content_filter": false }'
The new value applies to every step the resumed run still has to dispatch, and the 202 snapshot echoes it back. Steps already completed keep the results they produced. Sending the value already in force changes nothing, and a non-boolean is rejected with 400.
Eligibility is still checked at render time, so an account that is not entitled to turn the filter off does not fail here — the resumed run pauses again, this time with waitingReason: "permission_required".
#Confirm Cost
A workflow paused with cost_approval_required (for example a template run, which authorizes cost on start) or with cost_reauthorization_required (a run whose authorization expired mid-flight) is released through POST /:id/confirm-cost. The executor re-checks the authorization before each stage, so a run can pause a second time: a projected reservation that would breach the authorized tolerance pauses again with cost_approval_required and a delta preview. Both set awaitingCostApproval: true; a pause for any other reason returns 409 naming the reason it is actually waiting on.
For decision: "confirm", acceptedCostPreview is required and is tamper-checked against the preview the run persisted at pause time; a stale, mismatched, or expired preview is rejected with 409 (a missing or malformed object returns 400). A preview is honored for five minutes, so confirm within the same session the user saw the estimate in and re-read the workflow for a fresh preview rather than retrying an expired one. An optional Idempotency-Key / X-Idempotency-Key header makes the confirm safe to retry.
curl -X POST https://api.sogni.ai/v1/creative-agent/workflows/wf_.../confirm-cost \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"decision": "confirm",
"acceptedCostPreview": {
"totalEstimatedCapacityUnits": 18,
"tokenType": "spark",
"validityUntil": "2026-09-08T12:05:00.000Z"
}
}'
A confirmed decision returns 200 with { workflow, decision: "confirm", authorization } and dispatches the held steps on the same run. decision: "cancel" returns { workflow, decision: "cancel" } and cancels the pending work; a replayed Idempotency-Key returns { workflow, idempotent: true }.
#Reseed A Workflow
curl -X POST https://api.sogni.ai/v1/creative-agent/workflows/wf_.../reseed \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token_type": "spark",
"seed_overrides": {
"keyframe": 123456789
}
}'
Reseed clones a completed or partial_failure workflow plan, replaces seed values, starts a new durable workflow, and returns 201 with the new workflow plus reseed metadata:
{
"status": "success",
"data": {
"workflow": { "...": "..." },
"reseed": {
"cloned_from_run_id": "wf_...",
"steps": [
{ "stepId": "keyframe", "previousSeed": 111, "newSeed": 123456789 }
]
}
}
}
seed_overrides is optional. When omitted, every seedable step receives a fresh random seed. Reseed also accepts token_type and billing_mode; without them it keeps the source run's billing setting. Reseed uses the same active-workflow, global-capacity, and workflow-start rate limits as a new workflow start, with the same 409 and 429 responses.
A reseed mints new random seeds, so retrying one that timed out would start a second, different take. Send an Idempotency-Key (or X-Idempotency-Key) header to make it safe: a retry with the same key returns 200 with idempotent: true, the workflow the first request started, and the seeds that workflow actually received. Use a new key for each take you want. A key you already used for a different request returns 409.
#Cancel A Workflow
curl -X POST https://api.sogni.ai/v1/creative-agent/workflows/wf_.../cancel \
-H "Authorization: Bearer YOUR_API_KEY"
Cancellation is idempotent. If the workflow is already terminal, the API returns the current snapshot with transitioned: false.
#Structured Contracts + Permission Gate
Durable workflow execution shares the Structured Contracts dispatch behavior with /v1/chat/completions: gating policies, repair recipes, prompt contracts, typed media/session state, and the destructive-tool permission gate all apply. The currently hosted app/control subset is non-destructive. Future destructive tools default to blocked until a shared explicit-intent permission rule exists.
#Replay Records
/v1/creative-agent/workflows produces durable workflow records. /v1/chat/runs produces durable chat-run records for LLM-steered hosted tool turns. /v1/replay/records is a separate, lighter surface that captures one RunRecord per chat turn. See Chat Completions -> Replay Records for the endpoint shape.
- Workflows answer "what state is this multi-step plan in?" - durable, event-replay, cancellation, presigned media URLs.
- Chat runs answer "what state is this model-selected hosted tool turn in?" - durable LLM/tool rounds, event replay, cancellation, recovery, final response, and artifact refs.
- Replay records answer "what did the agent do for this user turn?" - the assistant message, the tool calls (with
cost_class+risk_levelchips from the shared per-tool cost metadata table), the tool results, and aggregated cost.
#Choosing Workflows
Use /v1/creative-agent/workflows when:
- Your app already knows the exact steps and tool arguments.
- You need durable workflow state, replayable event logs, SSE progress, or cooperative cancellation.
- You want to use uploaded Sogni artifact URLs or presigned HTTPS media URLs instead of inline chat
data:URIs. - You want deterministic orchestration rather than model-selected tools.
- You are building a production media pipeline where the UI should track each creative step independently of a chat response.
Use Durable Chat Runs when an LLM should interpret the user's request, choose hosted Sogni tools automatically, and still provide durable progress, event replay, cancellation, and recovery.
Use Chat Completions when an LLM should interpret the user's request and return a synchronous OpenAI-compatible response.
For agent runtime or CLI-style integration, use the public Sogni Creative Agent Skill. It wraps Sogni media generation as an installable agent skill and CLI while the REST workflow API remains the durable backend integration surface.