Generate images from text
generate turns a prompt into one to ten images, each stored as an asset with its own id and, if you publish it, a stable URL. Of the AI ops in the catalogue it is the one that takes no input image.
What it does
A model reads prompt and writes count images. Each becomes an asset, so the result is an id you can hand to the next op, publish, download or delete — not a data URL you have to find somewhere to put. The model's own knobs go in parameters: on the default model, aspectRatio and imageSize — and imageSize is also what it is priced by. The low end of its price range is its smallest size; a call that names none is priced at 1K, which the dry run shows before you spend.
Reach for it when the picture does not exist yet: illustrations for generated articles, product mock-ups, thumbnails per row of a spreadsheet. When you already have the image and want it changed, that is edit. When you want the same layout filled with different data — a price card, an OG image per post — do not generate it: render it from a template, which is deterministic, exact and free on a paid plan.
Call it
One op, six surfaces, one vocabulary: the op name below is the same string everywhere — the SDKs' named helpers only spell it in their language's case, ops.generate in JavaScript — because every surface enumerates GET /api/v1/ops instead of carrying its own list. The request is the catalogue's own example for this op, so it is the one the service has checked.
JavaScript
// pnpm add imagestep
const job = await client.ops.generate("a red bicycle on a white background", { count: 1, wait: true });
const [out] = await client.jobs.outputs(job);Python
# pip install imagestep
job = client.ops.generate("a red bicycle on a white background", count=1, wait=True)
[out] = client.jobs.outputs(job)CLI
imagestep jobs submit --op generate -p "a red bicycle on a white background" --count 1 --waitcurl
# "wait" holds the response until the job is done (60 s at most): 200 with the finished job,
# or 202 with the handle — then GET /api/v1/jobs/<id>?wait=30 waits again
curl -sS -X POST https://api.imagestep.dev/api/v1/jobs \
-H "Authorization: ApiKey $IMAGESTEP_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"op":"generate","prompt":"a red bicycle on a white background","count":1,"wait":30}'MCP
# tool call
generate {"prompt":"a red bicycle on a white background","count":1}n8n
{
"nodes": [
{
"parameters": {
"resource": "op",
"operation": "run",
"op": "generate",
"inputMode": "none",
"prompt": "a red bicycle on a white background",
"count": 1
},
"name": "ImageStep",
"type": "n8n-nodes-imagestep.imageStep",
"typeVersion": 1,
"position": [
0,
0
]
}
],
"connections": {}
}The n8n tab is a workflow to paste onto the canvas: the generate entry of the node's Op dropdown, which is filled from the same catalogue. Full references: SDKs, CLI, MCP, REST.
Parameters, model and price
Read live from GET https://api.imagestep.dev/api/v1/ops/generate — the same entry the SDKs, the MCP server and the n8n node enumerate, so this table cannot fall behind the API. The rows are fields of the request body. parameters goes to the model: its keys are the model's own, listed under parameters for each model in GET /api/v1/ai-models, and a key the model does not declare, or a value outside its options or bounds, is 400 invalid_param naming it.
generate
ai job type ai-generate · typically ~10 s per item · input scaled to ≤ 1536 px on the long edgeText → image. Returns one asset per generated image.
| Parameter | Type | What it does |
|---|---|---|
| prompt | string | What to draw. |
| count | integer | Images to generate (1–10). default 1 |
| model | string | A model whose categories include 'image_generate' — GET /api/v1/ai-models lists them. Any other model is 400 invalid_param, with the ones that fit in details.allowed. default google/gemini-3.1-flash-image-preview |
| parameters | object | Passed to the model; GET /api/v1/ai-models lists each model's own, and a key or value it does not declare is 400 invalid_param. The default model reads aspectRatio and imageSize — the output size, whatever the input's, and what its price depends on. |
AI op: per-item USD from the model's price; credits are charged per item. Price a batch with POST /api/v1/jobs?dryRun=true before spending. Default model google/gemini-3.1-flash-image-preview: $0.0567 - $0.1903 per item.
That is how it is priced. What one request costs — another model, a bigger scale factor, forty images — is the dry run, which prices the exact body you are about to send without creating anything.
In a workflow
The hard part of generating at scale is not the call, it is keeping a batch on-brand. A preset with subjects is the answer: the reference images pin the geometry, the descriptor pins the words, and every run expands {{subject.hero}} into the same description.
Save the subject once:
{
"name": "Bottle shots",
"slug": "bottle-shots",
"subjects": [
{
"name": "hero",
"referenceAssetIds": [
"<product-asset-id>"
],
"descriptor": "a matte black water bottle"
}
],
"steps": [
{
"op": "generate",
"prompt": "{{subject.hero}} on a rooftop at golden hour"
}
]
}JavaScript
import { readFile } from "node:fs/promises";
const preset = await client.presets.create(JSON.parse(await readFile("bottle-shots.json", "utf8")));
console.log(preset.slug, preset.version); // "bottle-shots" 1Python
import json
preset = client.presets.create(json.load(open("bottle-shots.json")))
print(preset["slug"], preset["version"]) # "bottle-shots" 1CLI
imagestep preset create -f bottle-shots.json -o jsoncurl
curl -s https://api.imagestep.dev/api/v1/presets -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" -d @bottle-shots.jsonMCP
# tool call — an agent has no disk to read, so the document travels in the call
save_preset {"name":"Bottle shots","slug":"bottle-shots","steps":[{"op":"generate","prompt":"{{subject.hero}} on a rooftop at golden hour"}],"subjects":[{"name":"hero","referenceAssetIds":["<product-asset-id>"],"descriptor":"a matte black water bottle"}]}Then every run is the same bottle. The preset starts from a prompt, so a run takes no assets — on every surface, the preset alone:
JavaScript
const price = await client.presets.run("bottle-shots", [], { dryRun: true }); // nothing is created
console.log(price.estimatedCredits, price.steps);
const job = await client.presets.run("bottle-shots", [], { wait: true });
const outputs = await client.jobs.outputs(job);Python
price = client.presets.run("bottle-shots", [], dry_run=True) # nothing is created
print(price["estimatedCredits"], price.get("steps"))
job = client.presets.run("bottle-shots", [], wait=True)
outputs = client.jobs.outputs(job)CLI
imagestep jobs estimate --preset-id bottle-shots
imagestep jobs submit --preset-id bottle-shots --wait -o jsoncurl
# price it — the same body, nothing is created
curl -s "https://api.imagestep.dev/api/v1/jobs?dryRun=true" -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
-d '{"presetId":"bottle-shots","assetIds":[]}'
# run it; "wait" holds the response until the job is done (60 s at most, then 202 and you wait again)
curl -s https://api.imagestep.dev/api/v1/jobs -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
-d '{"presetId":"bottle-shots","assetIds":[],"wait":30}'MCP
# tool calls — the first prices it and creates nothing
run_preset {"preset":"bottle-shots","dry_run":true}
run_preset {"preset":"bottle-shots"}Two more things worth wiring once: an idempotency key, so a retried request returns the first submission instead of paying twice, and a dry run in front of anything a user triggers, so you can refuse before you spend.
Limits
One to ten images per call, and the prompt is the model's contract, not ours — what it refuses to draw and how literally it reads you belong to the model you chose. Generated images carry no EXIF, so the capture-date filters mean nothing for them. When a provider is busy or rate-limits, the job retries the item itself; one that still fails settles as provider_unavailable — retryable, not charged — and is the one to resume, not a reason to change the prompt.
FAQ
- How many images can one call make?
- Up to ten, through count. Each one is a separate item of the job: it settles on its own, and an item that fails is not charged and does not cost you the rest.
- Can I keep a character or a product consistent across generations?
- Yes — that is what preset subjects are for. Save reference images and a written descriptor once, then write {{subject.name}} in the prompt; the images pin the geometry and the words pin the description.
- Which models can I use?
- Any model whose categories include image_generate — GET /api/v1/ai-models lists them with their categories; another is 400 invalid_param, with the ones that fit in details.allowed. Every model is priced per image in USD; /pricing lists them by op.
- How do I control the aspect ratio?
- Through parameters, which go to the model. The default model reads aspectRatio and imageSize; GET /api/v1/ai-models lists every model's own parameters with their allowed values. A key the model does not declare, or a value it does not take, is 400 invalid_param naming it — before anything is spent.
- Where does the image end up?
- As an asset in your account, with metadata and a thumbnail once ingest finishes. Publish it and it gets a stable CDN URL at full size; leave it unpublished and only your key can read it.