Windpaint
Workflows

Workflows

Pre-built multi-step pipelines you run with one call: list, inspect, estimate, run, poll, and cancel.

Coming soon. Workflows aren’t runnable yet: Text to Clip needs video.generate, which is coming soon. The examples below show how runs will work.

A workflow is a pipeline Windpaint has built for you. It chains several generation steps, and free processing steps like resizing an image or pulling a frame from a video, behind one request. You fill in the workflow’s inputs, start a run, and get back every named output when it finishes.

Text to Clip is the first workflow, and it’s coming soon: describe a moment, get a still, a 5 second clip, and a cover frame. Its clip step runs on wan-2.2-i2v, which isn’t live yet, so for now it reports available: false and can’t be started.

curl https://api.windpaint.ai/v1/workflows/text-to-clip/runs \
  -H "Authorization: Bearer $WINDPAINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"idea": "a paper boat drifting down a rainy street"}}'

Workflows are defined by Windpaint. You can’t create your own yet; to build a custom pipeline, chain generation jobs yourself by passing one job’s output asset id to the next.

Workflows and presets

Every workflow has a kind:

  • workflow: a full pipeline with its own inputs and steps.
  • preset: a workflow with some inputs already filled in, such as a fixed style or camera move. base names the workflow it’s built on. The fixed inputs don’t appear in the preset’s inputs, so you only fill in what’s left.

You run both the same way. Workflow presets are separate from generation presets, which style a single image.generate or video.generate job.

List and inspect workflows

curl
curl "https://api.windpaint.ai/v1/workflows?category=video" \
  -H "Authorization: Bearer $WINDPAINT_API_KEY"

curl https://api.windpaint.ai/v1/workflows/text-to-clip \
  -H "Authorization: Bearer $WINDPAINT_API_KEY"

category is optional and one of image, video, or audio. The list isn’t paginated. Both return workflows wrapped in {"data": ...}. The example shows Text to Clip as it will look once it’s live; today it has available: false, a reason in unavailable_reasons and credits_estimate: null:

{
  "data": {
    "slug": "text-to-clip",
    "name": "Text to Clip",
    "category": "video",
    "kind": "workflow",
    "description": "Describe a moment; get a still, a 5 s clip and a cover frame.",
    "thumbnail_url": null,
    "version": 1,
    "base": null,
    "inputs": {
      "idea": { "type": "text", "label": "What should happen?", "description": null, "required": true, "default": null, "options": [], "hidden": false },
      "aspect": { "type": "choice", "label": "Aspect ratio", "description": null, "required": true, "default": "16:9", "options": ["16:9", "9:16"], "hidden": false }
    },
    "outputs": ["video", "still", "cover"],
    "steps": [
      { "key": "still", "capability": "image.generate", "op": null, "model": "z-image-turbo" },
      { "key": "frame", "capability": null, "op": "image.resize", "model": null },
      { "key": "clip", "capability": "video.generate", "op": null, "model": "wan-2.2-i2v" },
      { "key": "cover", "capability": null, "op": "video.extract_frame", "model": null }
    ],
    "available": true,
    "unavailable_reasons": [],
    "credits_estimate": "4.08"
  }
}
inputsobject

The form to fill in, keyed by input name. Each input has a type, a label, whether it’s required, a default, and for choice inputs the allowed options. An input with a default can be left out even when required is true.

outputsstring[]

The names of the outputs a completed run returns.

stepsobject[]

What the run does. A step has either a capability (a generation job, billed) or an op (local processing, free).

availableboolean

Whether the workflow can run right now. See Availability.

credits_estimatestring | null

The total price using the inputs’ defaults. null when the workflow isn’t available.

Input types

Send inputs as {"inputs": {"<name>": <value>}}:

TypeValue
textA string.
numberA JSON number.
choiceOne of the input’s options, as a string.
image, video, audioOne reference as a string (not a list): an asset id, an asset URL returned by this API, a public https:// URL, or a data: URI. URLs and data URIs are fetched when the run starts and stored as assets in the run’s project; see Inputs. Assets must be in the run’s project and of the right kind.

Unknown input names, a missing required input, or a value of the wrong type return 422 request.validation_failed with the input name in details.

Estimate

Price a run with your actual inputs before you start it. Some choices change the tier a step runs at, so this can differ from the workflow’s default credits_estimate.

curl
curl https://api.windpaint.ai/v1/workflows/text-to-clip/estimate \
  -H "Authorization: Bearer $WINDPAINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"idea": "a paper boat drifting down a rainy street", "aspect": "9:16"}}'

The response is the workflow again, with available, unavailable_reasons, and credits_estimate worked out for these inputs. Inputs are validated the same way as for a run, so required inputs must be present.

Run

curl
curl https://api.windpaint.ai/v1/workflows/text-to-clip/runs \
  -H "Authorization: Bearer $WINDPAINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"idea": "a paper boat drifting down a rainy street", "aspect": "9:16"}}'

The API returns 202 with the new run in {"data": ...}. The CLI uploads any local files given as media inputs, waits for the run, and downloads every output into the -o directory. Add --no-wait to return as soon as the run starts.

The run goes into the project chosen the usual way: project_id in the body, then the X-Windpaint-Project header, then your default project.

A run start also takes:

  • webhook_url and webhook_events (started, completed) in the body, to get a signed POST with the run object when it starts or finishes. See Webhooks.
  • retention_days in the body, to keep the run’s outputs for less time than your organization’s retention setting.
  • An Idempotency-Key header, so a retried start returns the first run instead of starting a second one. See Idempotency.
  • A Prefer: wait=N header, to hold the response up to 60 seconds and get 200 with the finished run if it ends in time. Most runs with a video step take longer. See synchronous mode.
StatusErrorWhen
402billing.insufficient_creditsYour available balance is below the run’s estimate. details has required and available.
404request.not_foundNo workflow with that slug.
409idempotency.key_reused, idempotency.in_progressAn Idempotency-Key was reused with a different request, or its first request is still running.
422request.validation_failedBad inputs.
422input.*, generation.asset_goneA URL or data-URI input couldn’t be used, or an input asset has expired. See Inputs.
429rate_limit.requests, rate_limit.concurrencyA run start counts as one submit, and is refused while your organization is at its concurrent job limit. See Rate limits.
503request.service_unavailableThe workflow can’t run right now. details.reasons lists why.

Poll a run

Poll its status_url (GET /v1/workflows/runs/{id}) until status is terminal, pass a webhook_url when you start it, or open its event stream at GET /v1/workflows/runs/{id}/stream (see Stream a run). Text to Clip takes a few minutes, mostly the video step, so polling every 5 to 15 seconds is plenty.

curl
curl https://api.windpaint.ai/v1/workflows/runs/7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63 \
  -H "Authorization: Bearer $WINDPAINT_API_KEY"
{
  "data": {
    "id": "7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63",
    "status": "running",
    "workflow": "text-to-clip",
    "workflow_version": 1,
    "project_id": "5b0c8f4e-2d1a-4f7b-9c3e-8a6d2e1f0b44",
    "inputs": { "idea": "a paper boat drifting down a rainy street", "aspect": "9:16" },
    "steps": [
      { "key": "still", "capability": "image.generate", "op": null, "model": "z-image-turbo", "status": "completed", "request_id": "0b6f3a52-6a0e-4c63-9a52-2f0c1d7e9b11", "outputs": [ { "id": "d77a2c19-...", "url": "https://api.windpaint.ai/v1/generation/assets/d77a2c19-.../content", "content_type": "image/png", "width": 576, "height": 1024 } ], "error": null },
      { "key": "frame", "capability": null, "op": "image.resize", "model": null, "status": "completed", "request_id": null, "outputs": [ ... ], "error": null },
      { "key": "clip", "capability": "video.generate", "op": null, "model": "wan-2.2-i2v", "status": "running", "request_id": "3c9a5e17-8b2f-4d0c-a6e1-9f4b2d7c8e05", "outputs": [], "error": null },
      { "key": "cover", "capability": null, "op": "video.extract_frame", "model": null, "status": "pending", "request_id": null, "outputs": [], "error": null }
    ],
    "outputs": {},
    "credits": { "estimate": "4.08", "actual": null },
    "error": null,
    "status_url": "https://api.windpaint.ai/v1/workflows/runs/7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63",
    "cancel_url": "https://api.windpaint.ai/v1/workflows/runs/7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63/cancel",
    "created_at": "2026-10-04T14:10:02.118Z",
    "started_at": "2026-10-04T14:10:02.540Z",
    "finished_at": null
  }
}

Run statuses are queued, running, completed, failed, and canceled. Each step is pending, running, completed, failed, or canceled. Steps that don’t depend on each other run at the same time.

The run reports progress (0 to 1, from completed steps plus the reported progress of running ones) and timings, and each step has the same queue_position, progress, logs and timings fields as a job’s status.

A step’s outputs appear as soon as that step finishes. The run’s outputs, keyed by output name, and credits.actual are filled in when the whole run completes. Each output is a list of assets with the same shape as a job’s outputs; download them through their url with your key and curl -L.

Every capability step is an ordinary generation job with source: "workflow". Its request_id works with GET /v1/generation/requests/{id}/status, and windpaint requests --run <run-id> lists them.

If any step fails, the run stops: unfinished steps are canceled with error_code job.canceled_by_workflow, status becomes failed, and error names the step and the reason, for example clip: Generation failed: the model backend is unavailable. Credits were released. The run’s error_code is the failing step’s code, such as job.capacity_unavailable; branch on it rather than on error. A step that couldn’t be submitted at all carries the HTTP error code it was refused with, such as billing.insufficient_credits. See job error codes.

To list runs in the current project, newest first, use GET /v1/workflows/runs?limit=50&workflow=text-to-clip or windpaint runs --workflow text-to-clip. Runs also show up in the combined GET /v1/runs list alongside single jobs.

Cancel a run

curl
curl -X POST https://api.windpaint.ai/v1/workflows/runs/7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63/cancel \
  -H "Authorization: Bearer $WINDPAINT_API_KEY"

Canceling marks the run and its unfinished steps canceled with error_code job.canceled_by_user, and cancels any generation job still in flight, releasing its hold. Canceling a run that already finished returns it unchanged.

Billing

You pay for each capability step that completes, at the same price as running it yourself. Op steps (resize, extract frame, and so on) are free.

Starting a run checks that your available balance covers the whole estimate, but doesn’t reserve it. Each capability step holds its own credits when it starts and is charged when it completes. That means:

  • A run can fail partway if your balance runs out between steps, for example because other jobs spent it.
  • Steps that completed before a failure or a cancel stay charged. Steps that failed or never ran cost nothing.

See Pricing for holds and current prices.

Versions

A workflow has a version that goes up whenever Windpaint changes its definition. Each run records the workflow_version it started with and keeps running that version to the end, even if the workflow changes mid-run. You can’t choose an older version when starting a run.

Availability

A workflow is available: false when one of its steps has no live model or no price for the tier it needs. unavailable_reasons lists each blocked step, and starting a run returns 503 with the same reasons in details.reasons.

No workflow is available yet. Text to Clip is coming soon, once wan-2.2-i2v is live. The other workflows in the catalog, such as Photo to Clip and its presets and Product to Ad, depend on capabilities that don’t have a model yet and report available: false until those models ship.