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 run | stdout |
|---|---|
windpaint assets in a terminal | Table |
windpaint assets | jq or > file | JSON |
windpaint assets --json (or --format json) | JSON, even in a terminal |
windpaint assets --format text | Table, 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:
| Command | Adds |
|---|---|
run, status, runs get, products run | downloads: one {asset_id, path, content_type, size_bytes} per saved file, when you pass -o |
estimate | available_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'"
}
}
| Field | Present | Description |
|---|---|---|
class | Always | CLI error class, e.g. usage, auth, not_found, insufficient_credits, invalid_request, unavailable, timeout, interrupted, job_failed, run_failed. |
exit_code | Always | Same as the process exit code. |
message | Always | Human-readable message. |
code | API errors | The API’s stable error code. See Errors. |
status | API errors | HTTP status. |
details | When the API sent them | E.g. required and available credits, or validation details. |
request_id | When the API sent one | Include it when you contact support. |
hint | Sometimes | What 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.
| Code | Meaning |
|---|---|
0 | Success |
1 | Unclassified error |
2 | Usage: bad flags or arguments, a missing local file, a file over 50 MiB, or an output file that exists without --force |
3 | Auth: no key, an invalid or revoked key, or not permitted (HTTP 401/403) |
4 | Not found (HTTP 404, or an unknown capability or project) |
5 | Not enough credits (HTTP 402, billing.insufficient_credits) |
6 | The API rejected the request (HTTP 400/409/422), e.g. an unsupported resolution or a capability with no model yet |
7 | Rate limited (HTTP 429). The API doesn’t rate-limit today; the code is reserved. |
8 | API unreachable, or an HTTP 5xx (including a model or product that is temporarily unavailable) |
9 | The job or run finished failed, nsfw or canceled. The final status is on stdout. |
10 | Still running when --timeout passed. Keep waiting with status --wait or runs get --wait. |
130 | Interrupted (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 runauth login. - Read JSON. Agents capture stdout through a pipe, so output is JSON already. Pass
--jsonanyway to be explicit, and-qto keep progress out of the transcript. - Branch on the exit code, then read
error.codefrom 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 estimatetakes the same flags asrunand prints the credits and your balance without uploading or spending anything. - Don’t hold a turn on video. Use
--no-wait, thenstatus --wait --timeout 10m, and handle exit code 10 by waiting again. - Never resubmit blindly. After exit code 8 or 130, check
windpaint requestsbefore running again. - Keep keys out of prompts and logs.
config showprints the key redacted; never echoWINDPAINT_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.