Products
Pre-built multi-step pipelines you run with one call: list, inspect, estimate, run, poll, and cancel.
A product 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 product’s inputs, start a run, and get back every named output when it finishes.
Text to Clip is the product you can run today: describe a moment, get a still, a 5 second clip, and a cover frame.
curl https://api.windpaint.ai/v1/workflows/products/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"}}'
Products 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 product 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.basenames the workflow it’s built on. The fixed inputs don’t appear in the preset’sinputs, so you only fill in what’s left.
You run both the same way.
List and inspect products
category is optional and one of image, video, or audio. The list isn’t paginated. Both return products wrapped in {"data": ...}:
{
"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"
}
}
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.
The names of the outputs a completed run returns.
What the run does. A step has either a capability (a generation job, billed) or an op (local processing, free).
Whether the product can run right now. See Availability.
The total price using the inputs’ defaults. null when the product isn’t available.
Input types
Send inputs as {"inputs": {"<name>": <value>}}:
| Type | Value |
|---|---|
text | A string. |
number | A JSON number. |
choice | One of the input’s options, as a string. |
image, video, audio | One asset id, or one asset URL returned by this API, as a string (not a list). The asset must be in the run’s project and of the right kind. Upload local files first. |
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 product’s default credits_estimate.
The response is the product 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
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.
| Status | Error | When |
|---|---|---|
402 | billing.insufficient_credits | Your available balance is below the run’s estimate. details has required and available. |
404 | request.not_found | No product with that slug. |
422 | request.validation_failed | Bad inputs. |
503 | request.service_unavailable | The product can’t run right now. details.reasons lists why. |
Poll a run
A run has no webhook. Poll its status_url (GET /v1/workflows/runs/{id}) until status is terminal. Text to Clip takes a few minutes, mostly the video step, so polling every 5 to 15 seconds is plenty.
{
"data": {
"id": "7c1e9a40-3b2d-4f6a-8e15-0d9c4b7a2f63",
"status": "running",
"product": "text-to-clip",
"product_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.
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, status becomes failed, and error names the step and the reason, for example clip: Generation failed: the model backend is unavailable. Credits were released. Generation steps use the job failure messages.
To list runs in the current project, newest first, use GET /v1/workflows/runs?limit=50&product=text-to-clip or windpaint runs --product text-to-clip. Runs also show up in the combined GET /v1/runs list alongside single jobs.
Cancel a run
Canceling marks the run and its unfinished steps canceled 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 product has a version that goes up whenever Windpaint changes its definition. Each run records the product_version it started with and keeps running that version to the end, even if the product changes mid-run. You can’t choose an older version when starting a run.
Availability
A product 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.
Text to Clip is available today. The other products 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.