Source: https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/

# 🎨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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#workflow-endpoints)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`](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#start-request)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](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#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. |

### [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#sensitive-content-filter)Sensitive Content Filter

The filter is a **per-request** setting on the API and defaults to on. The [Sensitive Content Filter](https://docs.sogni.ai/sogni-create/studio/advanced/safe-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](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#resume-a-workflow).

### [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#plan-or-tokens)Plan or tokens

Two fields decide how a workflow is paid, and they control different things:

-   `token_type` picks **which token** pays when tokens are billed: Spark or SOGNI.
-   `billing_mode` picks **whether an active [Unlimited plan](https://docs.sogni.ai/pricing/unlimited-plan-details/) 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](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#resume-a-workflow) and [reseed](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#reseed-a-workflow) 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.

### [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#limits-errors-and-retries)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`](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#stream-events) 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](https://docs.sogni.ai/api-reference/production-integrations/). Need higher limits? Tell us what you're building at [hello@sogni.ai](mailto:hello@sogni.ai).

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#step-shape)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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#lora-steps)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 `400`s 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](https://docs.sogni.ai/api-reference/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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#h3-reference-audio-policy)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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#planning-from-a-brief)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`](https://docs.sogni.ai/sogni-intelligence/workflow-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](https://docs.sogni.ai/sogni-intelligence/chat-completions/#workflow-planning) for the end-to-end Plan → Review → Execute example.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#run-from-a-template)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`](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#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](https://docs.sogni.ai/sogni-intelligence/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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#generated-keyframe-to-video)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](https://www.sogni.ai/models/seedance-2-5) (`videoModel: "seedance2-5"`), Seedance 2.0 (`videoModel: "seedance2"` or `"seedance2-mini"`), [Alibaba HappyHorse 1.1](https://www.sogni.ai/models/happyhorse-1-1) (`videoModel: "happyhorse-1.1-t2v"`, `"happyhorse-1.1-i2v"`, or `"happyhorse-1.1-r2v"`), [Alibaba Wan 3](https://docs.sogni.ai/models/wan-3/) (`videoModel: "wan3.0-video"`), and [Wan 3 Uncensored](https://docs.sogni.ai/models/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
    }
  }
}
```

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#request-media)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](https://www.sogni.ai/models/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](https://docs.sogni.ai/sogni-intelligence/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"
  }'
```

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#chained-media-steps)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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#sogni-agent-cli)Sogni Agent CLI

The public [Sogni Creative Agent Skill](https://docs.sogni.ai/sogni-intelligence/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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#stream-events)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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#resume-a-workflow)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](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#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"`.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#confirm-cost)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 }`.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#reseed-a-workflow)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](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#limits-errors-and-retries).

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`.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#cancel-a-workflow)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`.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#structured-contracts-permission-gate)Structured Contracts + Permission Gate

Durable workflow execution shares the [Structured Contracts dispatch](https://docs.sogni.ai/sogni-intelligence/chat-completions/#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.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#replay-records)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](https://docs.sogni.ai/sogni-intelligence/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_level` chips from the shared per-tool cost metadata table), the tool results, and aggregated cost.

## [#](https://docs.sogni.ai/sogni-intelligence/creative-agent-workflows/#choosing-workflows)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](https://docs.sogni.ai/sogni-intelligence/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](https://docs.sogni.ai/sogni-intelligence/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](https://docs.sogni.ai/sogni-intelligence/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.
