Windpaint
Get started

Scripting and agents

JSON output, stderr progress, the error shape, exit codes, and patterns for scripts and coding agents.

Every windpaint command behaves the same in a terminal, a shell script and an agent. Only the rendering changes: tables for people, JSON for programs.

Output modes

stdout carries the result. In a terminal it’s a table; when stdout is piped or redirected it’s JSON, so windpaint assets | jq works with no flags.

You runstdout
windpaint assets in a terminalTable
windpaint assets | jq or > fileJSON
windpaint assets --json (or --format json)JSON, even in a terminal
windpaint assets --format textTable, even when piped

The JSON is the API’s payload as returned, without the {"data": ...} wrapper, so field names match the API reference. A list prints as an array, a single resource as an object. A few commands add fields:

CommandAdds
run, status, runs get, products rundownloads: one {asset_id, path, content_type, size_bytes} per saved file, when you pass -o
estimateavailable_credits and held_credits from your balance

config show, version, download, projects select and auth logout print their own small objects, shown on each command’s page.

Progress goes to stderr

Upload progress, “Submitted …”, job status and “Saved …” lines all go to stderr, never stdout. Redirecting stdout gives you clean data while progress still shows:

windpaint run image.generate "a lighthouse at dusk" -o ./out/ > result.json
Submitted 0b6e2f4a-91c3-4d8e-a5f7-3c2d1e0f9a8b (image.generate, estimate 0.08 credits)
0b6e2f4a-91c3-4d8e-a5f7-3c2d1e0f9a8b completed in 4s
Saved out/7d2c9e1a-5b3f-4a6d-8e0c-1f2a3b4c5d6e.png

In a terminal, the status line updates in place. When stderr isn’t a terminal (CI logs, agents), a line prints only when the status changes, so logs stay short.

-q / --quiet silences progress. Errors still print.

Errors

On failure, stdout is empty and the error goes to stderr. In a terminal it’s text:

error: Not enough credits: this needs 4 and 2 are available. [billing.insufficient_credits, HTTP 402]
  available: 2
  required: 4
hint: top up in the Windpaint dashboard, or pick a cheaper tier and check 'windpaint estimate'

In JSON mode it’s a single JSON object:

{
  "error": {
    "class": "insufficient_credits",
    "code": "billing.insufficient_credits",
    "status": 402,
    "message": "Not enough credits: this needs 4 and 2 are available.",
    "details": { "required": "4", "available": "2" },
    "request_id": "86926749.37e5a9",
    "exit_code": 5,
    "hint": "top up in the Windpaint dashboard, or pick a cheaper tier and check 'windpaint estimate'"
  }
}
FieldPresentDescription
classAlwaysCLI error class, e.g. usage, auth, not_found, insufficient_credits, invalid_request, unavailable, timeout, interrupted, job_failed, run_failed.
exit_codeAlwaysSame as the process exit code.
messageAlwaysHuman-readable message.
codeAPI errorsThe API’s stable error code. See Errors.
statusAPI errorsHTTP status.
detailsWhen the API sent themE.g. required and available credits, or validation details.
request_idWhen the API sent oneInclude it when you contact support.
hintSometimesWhat to do next.

Branch on the exit code first, then on code when you need the exact reason.

When a job or run finishes failed, nsfw or canceled, or a wait times out, the final status still prints on stdout and the error goes to stderr, so you can read both.

Exit codes

These are stable. Scripts and agents can rely on them.

CodeMeaning
0Success
1Unclassified error
2Usage: bad flags or arguments, a missing local file, a file over 50 MiB, or an output file that exists without --force
3Auth: no key, an invalid or revoked key, or not permitted (HTTP 401/403)
4Not found (HTTP 404, or an unknown capability or project)
5Not enough credits (HTTP 402, billing.insufficient_credits)
6The API rejected the request (HTTP 400/409/422), e.g. an unsupported resolution or a capability with no model yet
7Rate limited (HTTP 429). The API doesn’t rate-limit today; the code is reserved.
8API unreachable, or an HTTP 5xx (including a model or product that is temporarily unavailable)
9The job or run finished failed, nsfw or canceled. The final status is on stdout.
10Still running when --timeout passed. Keep waiting with status --wait or runs get --wait.
130Interrupted (Ctrl-C). The job or run keeps running on the server.

Patterns

Chain one output into the next

Every output is an asset with an id. Pass that id as an input to the next command, and nothing is downloaded or re-uploaded in between:

id=$(windpaint run image.generate "product shot of a sneaker on white" --json | jq -r '.outputs[0].id')
windpaint run video.generate -p "slow 360 turntable" -i start_frame="$id" -o sneaker.mp4

Submit now, collect later

Video takes minutes. Submit with --no-wait, do other work, then wait and download:

req=$(windpaint run video.generate -p "slow dolly in" -i start_frame=./frame.png --no-wait --json | jq -r '.request_id')
# ...
windpaint status "$req" --wait -o clip.mp4

--no-wait prints the API’s submit response: status, request_id, status_url, cancel_url and credits_estimate.

Bound the wait

run and status --wait wait up to 15 minutes by default; products run and runs get --wait up to 30. Set --timeout (0 waits forever). When the timeout passes the job is still running, and the command exits with code 10:

windpaint status "$req" --wait --timeout 5m -o clip.mp4
case $? in
  0)  echo "done" ;;
  10) echo "still rendering; try again later" ;;
  9)  echo "job failed" ;;
  *)  echo "error" ;;
esac

Ctrl-C doesn’t cancel

Interrupting a wait exits with code 130 and leaves the job running on the server; you’re still charged when it completes. The error tells you how to stop it:

error: interrupted; request 0b6e2f4a-91c3-4d8e-a5f7-3c2d1e0f9a8b keeps running
hint: windpaint cancel 0b6e2f4a-91c3-4d8e-a5f7-3c2d1e0f9a8b

Use windpaint cancel <request-id> for a generation request and windpaint runs cancel <run-id> for a product run.

Retry safely

The API has no idempotency keys: if run fails with a network error or exit code 8 after submitting, the job may still have been created and billed. Before you resubmit, check:

windpaint requests -n 5

Exit code 6 means the request itself is wrong; retrying won’t help. Fix the flags (check windpaint capabilities get <capability>), then run again.

Download everything in a project

windpaint download $(windpaint assets -n 200 --json | jq -r '.[].id') -o ./all/

Coding agents

The CLI is a good fit for agents like Claude Code, Codex and Cursor: one command does upload, submit, wait and download, and the output is machine-readable. A few rules make it reliable:

  • Authenticate with an environment variable. Set WINDPAINT_API_KEY=aak_... in the agent’s environment. Don’t have the agent run auth login.
  • Read JSON. Agents capture stdout through a pipe, so output is JSON already. Pass --json anyway to be explicit, and -q to keep progress out of the transcript.
  • Branch on the exit code, then read error.code from stderr. Don’t parse the text messages.
  • Discover before running. windpaint capabilities get <capability> lists the input slots, models, resolutions and prices; windpaint products get <slug> lists a product’s inputs.
  • Estimate first when cost matters: windpaint estimate takes the same flags as run and prints the credits and your balance without uploading or spending anything.
  • Don’t hold a turn on video. Use --no-wait, then status --wait --timeout 10m, and handle exit code 10 by waiting again.
  • Never resubmit blindly. After exit code 8 or 130, check windpaint requests before running again.
  • Keep keys out of prompts and logs. config show prints the key redacted; never echo WINDPAINT_API_KEY.

An agent instruction block you can drop into AGENTS.md or CLAUDE.md:

## Windpaint
- Use the `windpaint` CLI for image and video generation. WINDPAINT_API_KEY is set.
- Always pass `--json -q`. Read results from stdout; on failure read the JSON error on stderr.
- Run `windpaint capabilities get <capability>` before using a capability you haven't used.
- Write outputs under ./assets/ with `-o ./assets/`.
- For video, submit with `--no-wait`, then `windpaint status <id> --wait --timeout 10m -o ./assets/`.
  Exit 10 means still running: wait again, don't resubmit.
- Exit 5 means out of credits: stop and tell me.

If you’d rather give an agent tools than a CLI, see Agents for the MCP server and the agent skills plugin.

Next