TL;DR
- Run
openart model list, then quote pricing withopenart model cost --model <id> --mode text2image. - Preview one request with
openart generate image "<prompt>" --model <id> --dry-run. - Use a bash loop to call
openart generate image "$prompt" --model <id> --json --asyncfor every prompt. - Parse each JSON response, save its job ID, and collect results later with
openart creation wait <job-id>or check once withopenart creation get <job-id>. - Choose this workflow for repeatable bulk jobs, CI pipelines, or shell-based agents — the same pattern extends to
openart generate videofor batch video generation, not just images. The web UI suits exploratory work, while OpenArt MCP suits conversational generation.
Before you script anything
This tutorial assumes the binary is installed and authenticated. If not, install it with one command and sign in through the browser.
curl -fsSL https://raw.githubusercontent.com/OpenArt-AI/cli/main/install.sh | sh
openart login
Confirm the account, plan, and credit balance before queuing a batch that spends real credits.
openart account
For Windows install steps, pinning a specific version, or the full command reference, see the CLI GitHub repo rather than repeating setup here.
Check model fit and cost before you spend anything
Choose a model that supports text-to-image generation before you build the batch loop. Model availability and capabilities can change as OpenArt adds new options, so avoid relying on a remembered name or an implicit default.
openart model list
Review the available models, confirm text-to-image support, and copy the model ID you want to use. Store the ID in a variable to reduce editing mistakes in later commands.
If you need to confirm exactly which parameters a model accepts before adding them to a batch, such as width, height, or style fields, check its form.
openart model form "$MODEL_ID" text2image
MODEL_ID="<model-id>"
Check the model's per-image price before submitting any jobs.
openart model cost \
--model "$MODEL_ID" \
--mode text2image
The model cost command only returns a quote. It does not generate an image or spend credits. Run it with no flags to list every model's price cheapest first, or scope it to one model and mode as shown above. Multiply the quoted per-image cost by the number of planned images to estimate the batch cost. If your CSV assigns different models to different rows, request a quote for every model and calculate each group separately.
Preview the exact request with --dry-run
Run one representative prompt with the same model and flags planned for the batch.
MODEL_ID="your-model-id"
openart generate image \
"A red bicycle leaning against a brick wall at sunset" \
--model "$MODEL_ID" \
--json \
--async \
--dry-run
The command prints the request OpenArt would send, including the prompt, model, and generation parameters. OpenArt does not submit a job or spend credits during a dry run.
Cost quoting estimates the price of each image. The dry run checks the request itself. Add any parameters intended for the batch to this command, inspect the output, and correct unexpected defaults or quoting problems before reading the real prompt file.
Format the prompt list
Use prompts.txt when every job shares the same model and parameters. Store one complete prompt per line.
A watercolor cabin beside a frozen lake
A studio photo of a red ceramic teapot
An isometric library with warm lighting
Keep each prompt on one physical line. A later read -r loop preserves backslashes, and quoting "$prompt" passes spaces and quotation marks as part of the prompt. Skip blank lines unless you intend to submit an empty prompt.
Use prompts.csv when rows need different models or dimensions.
prompt,model,width,height
"A cabin beside a lake","model-id-a",1024,1024
"A teapot, red ceramic","model-id-b",768,1024
"A sign reading ""OPEN""","model-id-a",1024,768
CSV fields containing commas must use double quotes. Represent a quotation mark inside a quoted field with two quotation marks. A shell loop based on IFS=, cannot parse those cases correctly, so the CSV variant should use a CSV-aware parser such as Python's csv module. Keep the header names stable because the later script reads them directly.
Submit the batch with a while-read loop
Save the following script as submit.sh. It reads one prompt per line, submits each generation, and appends every returned job ID to job-ids.txt.
#!/usr/bin/env bash
set -uo pipefail
MODEL_ID="replace-with-model-id"
PROMPT_FILE="${1:-prompts.txt}"
JOB_FILE="${2:-job-ids.txt}"
printf '' > "$JOB_FILE"
while IFS= read -r prompt || [[ -n "$prompt" ]]; do
[[ -z "$prompt" ]] && continue
if ! response=$(openart generate image "$prompt" \
--model "$MODEL_ID" \
--json \
--async); then
printf 'Submission failed for prompt %s\n' "$prompt" >&2
continue
fi
if ! job_id=$(jq -er '.id' <<&2
continue
fi
printf '%s\n' "$job_id" >> "$JOB_FILE"
printf 'Submitted %s\n' "$job_id" >&2
done &2
Replace replace-with-model-id, make the script executable, and pass a different prompt file if needed.
chmod +x submit.sh
./submit.sh prompts.txt
The script requires jq to extract the ID from each JSON response. If your installed CLI returns the identifier under a different field, inspect one response and adjust the .id expression.
The --json flag keeps standard output machine-readable. OpenArt sends progress and pagination messages to standard error, so command substitution captures only the JSON that jq needs. Without --json, human-readable output could enter the response variable and cause ID parsing to fail.
The --async flag makes each command return after OpenArt accepts the job. Without it, the loop waits for one image before submitting the next and may wait until the default five-minute timeout for every prompt. Async submission lets the loop enqueue the full batch first.
Each successful submission adds one identifier to job-ids.txt.
job_abc123
job_def456
job_ghi789
The collection loop can consume that file directly. Failed submissions produce an error on standard error and do not add an invalid entry to the file.
Run the CSV variant
The CSV loop splits each row into fields and passes the row's model to the generation command.
#!/usr/bin/env bash
> job-ids.txt
tail -n +2 prompts.csv |
while IFS=, read -r prompt model
do
model=${model%$'\r'}
[ -z "$prompt" ] && continue
response=$(
openart generate image "$prompt" \
--model "$model" \
--json \
--async
) || continue
printf '%s\n' "$response" |
jq -r '.id' >> job-ids.txt
done
Compared with the plain-text loop, IFS=, read -r prompt model splits each row, and --model "$model" replaces the fixed model ID. The tail command skips a header row such as prompt,model.
Bash field splitting does not implement full CSV quoting rules. If prompts contain commas or escaped quotes, parse the file with Python's csv module or another CSV-aware tool before passing fields to the OpenArt command.
Collect results once everything is submitted
Use openart creation wait when the script must finish only after every submitted job reaches a terminal state. The loop saves each completed creation record as JSON and records any jobs that return an error.
mkdir -p results
: > failed_job_ids.txt
while IFS= read -r job_id; do
[ -z "$job_id" ] && continue
if openart creation wait "$job_id" --json \
> "results/${job_id}.json"; then
printf 'Completed %s\n' "$job_id"
else
printf '%s\n' "$job_id" >> failed_job_ids.txt
printf 'Failed %s\n' "$job_id" >&2
fi
done < job_ids.txt
Each JSON file contains the final creation data, including the returned asset information. Your next processing step can read those files and download or move the generated images as needed.
Use openart creation get when you want one status check without blocking. A manual check-in or scheduled polling job can run the same loop with one command changed.
mkdir -p status
while IFS= read -r job_id; do
[ -z "$job_id" ] && continue
openart creation get "$job_id" --json \
> "status/${job_id}.json"
done < job_ids.txt
The wait loop pauses on each ID, but it does not make image generation serial. Every job started after the earlier async submission, so later jobs continue running while the loop waits for the first one. In CI, wait provides a clear completion point. For manual monitoring, get lets you inspect current states and return immediately.
What else the OpenArt CLI can do
This tutorial focuses on one workflow: turning a list of text prompts into a batch of images. The CLI covers more ground than that, and it's worth knowing what's there before you build separate tooling for it.
It can also change an image you already have. Point it at a photo on your computer or a link, describe the edit, and get a new version back, instead of only generating from scratch. On the video side, it makes a video from a description, or brings a still photo to life, with the length, shape, and resolution under your control wherever the chosen model allows it.
Beyond generation, the CLI keeps you organized: switch between projects and workspaces, and upload a reference image once to reuse it across later prompts instead of re-uploading the same file every time. Every result can be kept as a shareable link or downloaded straight into a folder, and you can look back over anything you've made, check a job that's still running, or wait for one to finish.
None of that changes the batch pattern in this tutorial. It just means the same terminal-first approach extends past plain text-to-image jobs. See OpenArt's CLI overview for the commands behind each of these.
Where this batch workflow actually gets used
A few concrete cases explain why teams reach for a scripted batch instead of the web app.
Product catalog variants. An e-commerce team with a spreadsheet of 500 SKUs generates one product image per row by feeding product descriptions through the CSV variant, with each row's model and dimensions matched to where the image will run (square for a product grid, portrait for a story ad).
Content asset walls. A content or growth team needs 50 thumbnail variations for an A/B test before lunch. A text-file prompt list and the while-read loop submit all 50 in the time it takes to write the prompts, instead of clicking through the web app 50 times.
Localized creative sets. A marketing team turns the same base concept into a set of ad creative variants across a dozen prompts, each with a different setting, model, or aspect ratio for a specific market or channel, using the CSV variant to vary those fields per row.
CI and agent pipelines. A nightly job regenerates a fixed set of preview images whenever source data changes, or an agent that already runs shell commands submits generations as part of a larger tool-calling workflow — the same pattern behind generating product ads from Claude. Both need --json output and non-interactive exit codes, not a browser session.
When to reach for the CLI batch workflow instead of the web app or MCP
Use the CLI batch workflow when a script needs to submit repeatable jobs, preserve job IDs, and collect results without manual input. It fits scheduled CI runs, shell pipelines, bulk prompt processing, and agent tool calls that execute terminal commands.
Use OpenArt for one-off creative sessions where you want to adjust prompts and settings while reviewing each result. OpenArt MCP fits conversational generation inside agents such as Claude or ChatGPT, where the conversation controls the request instead of a shell script.
If the goal is not just generating images but also routing each result somewhere else automatically, such as posting a finished image to Telegram, Slack, or Discord, that behavior belongs to an agent, not to this batch script. An agent framework that already supports tool calling can call OpenArt MCP to generate the image, then call a separate messaging tool to deliver it. The while-read loop in this tutorial submits jobs and writes results to disk. It does not send anything anywhere, so a delivery step still needs its own script or agent on top of it.
Choose the interface that matches how you work. The CLI GitHub repo covers installation and the full command reference. The MCP overview above covers chat-based generation, if that fits your workflow better than file-driven batch processing.
FAQs
What happens if a job fails mid-batch?
A failed job does not cancel jobs already submitted. Your collection loop should record the failed job ID and continue unless the script exits on errors. Inspect the failed job with openart creation get <job-id>.
How do I set a longer timeout without --async?
The --timeout <seconds> option extends how long a synchronous generation command waits. For example, add --timeout 900 to wait up to 15 minutes. Confirm the supported value with openart generate image --help.
Can I use --async with -o or --output?
Async submission returns a job ID before the image exists, so the generate command cannot save the finished image immediately. Store the job ID during submission instead. Use openart creation wait <job-id> afterward to collect the result and handle its output.
How do I rerun only failed prompts?
A rerun requires a record that maps each prompt to its submitted job ID. During collection, write failed IDs and their prompts to a separate file. Feed that file back into the submission loop after correcting any invalid prompts or parameters.
Will submitting hundreds of jobs at once hit a rate limit?
The loop as written submits as fast as the shell can iterate, which can outrun a rate limit on a large batch. Add a short sleep 0.5 inside the loop after each submission, or track a counter and pause for a few seconds every 20 to 50 prompts, to keep the request rate steady instead of bursting.
Does --dry-run work together with --async?
Yes. Combining them previews the exact request for a job that would otherwise run asynchronously, without submitting it or spending credits. Keep both flags on the sample command while testing, then drop only --dry-run when you move to the real batch.
How do I keep track of which image came from which prompt?
Job IDs alone do not carry the original prompt text. Write the prompt and its job ID to the same line of a log file at submission time, for example printf '%s\t%s\n' "$job_id" "$prompt" >> submissions.tsv, so a later step can match a finished image back to the prompt that produced it.
Where do the finished images end up?
openart creation wait and openart creation get return job metadata and asset URLs in JSON, not the image files themselves. Add a download step, such as curl -o "results/${job_id}.png" "$url" using the URL from that JSON, if the workflow needs files on disk rather than links.
Does this batch workflow work for video generation too?
Yes. The CLI mirrors the image commands for video: openart generate video "<prompt>" --model kling-3-omni submits a video job the same way openart generate image submits an image job. Swap generate image for generate video in the submission loop, and the same --json/--async flags plus openart creation wait/openart creation get collection pattern should apply. Run openart generate video --help to confirm any video-specific flags (such as duration) before scripting a large batch, since this tutorial's examples and testing cover image generation specifically.