官方原文全文
OpenRouter Image Generation: A Code-First API Tutorial — OpenRouter Blog
OpenRouter ·8/17/2026
Tl;drTl;drPrerequisitesStep 1: Get a key and choose an image modelStep 2: Send your first image requestStep 3: Decode and save output.pngStep 4: Add a reference imageStep 5: Make the request reusableTroubleshooting and cost notesFrequently asked questionsReferences
Adding image generation to an app gets harder when you need to support more than one provider. Dozens of image models across many providers use different endpoints, data formats, controls, and billing models, with charges calculated per image, megapixel, or token.
We address this integration problem with a dedicated Image generation API that uses one request format and one key across supported models.
Tl;dr
- One API and one key reach supported image models through POST /api/v1/images.
- Buffered responses place the generated image in data[0].b64_json, which you decode and save locally.
- Compatible models accept an optional reference image through input_references.
In this guide, you’ll build runnable Python and JavaScript flows that send a prompt, decode and save the returned image, then pass a reference image to the endpoint and save the generated variation.
Prerequisites
Before you start, have the following ready:
- An OpenRouter account. You’ll create the API key in Step 1.
- Python 3 with the requests package, or Node 18+ with built-in fetch.
Step 1: Get a key and choose an image model
Create a key on the keys page, then export it in the same terminal you’ll use to run the script.
export OPENROUTER_API_KEY="sk-or-v1-..."
Choose a model from the image models collection. Let’s start with bytedance-seed/seedream-4.5 for the first run.
You can switch between image-capable models by changing the model string. Optional controls such as resolution, multiple outputs, and reference inputs vary by model, so check the capability record before adding them.
For runtime discovery, GET /api/v1/images/models returns image-capable slugs and supported parameters. Each model also has endpoint records with provider-specific capabilities and pricing. You don’t need those endpoints for this tutorial, but they’re useful once this script becomes a product feature.
Step 2: Send your first image request
Send a POST request to https://openrouter.ai/api/v1/images. The Image API requires two body fields. model selects an image-capable model, while prompt describes the image you want. Authenticate with your OpenRouter key in the Bearer header.
Python
import os
import requests
response = requests.post(
"https://openrouter.ai/api/v1/images",
headers={
"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seed/seedream-4.5",
"prompt": "A studio product photo of a matte black travel mug on a light gray background",
},
timeout=120,
)
if not response.ok:
raise RuntimeError(f"{response.status_code}: {response.text}")
result = response.json()
Checking response.ok before parsing keeps API errors visible instead of turning them into confusing missing-field errors later.
JavaScript
const response = await fetch("https://openrouter.ai/api/v1/images", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "bytedance-seed/seedream-4.5",
prompt: "A studio product photo of a matte black travel mug on a light gray background",
}),
});
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`);
}
const result = await response.json();
The explicit error check preserves the response body, which usually contains the detail you need to fix the request.
Where is the image in the response?
A successful buffered response follows this shortened shape:
{
"data": [
{
"b64_json": "iVBORw0KGgoAAA...",
"media_type": "image/png"
}
],
"usage": {
"cost": 0.0123
}
}
The example cost only demonstrates the field shape. It’s not a current price.
data is an array because one request can return multiple results. The first image sits at data[0].b64_json. That value contains base64-encoded bytes, not a hosted URL. media_type appears when we can identify the output format. The optional usage.cost value reports the completed request cost when available.
For now, a nonempty b64_json value confirms that generation succeeded. The next step turns those bytes into a local image file.
Step 3: Decode and save output.png
The response shows that generation worked, but the image is still base64 text inside JSON. The next step is to decode b64_json into bytes and write those bytes to disk. Both scripts below repeat the request so you can run either file independently.
Python
Save this as generate.py:
import base64
import os
import requests
response = requests.post(
"https://openrouter.ai/api/v1/images",
headers={
"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "bytedance-seed/seedream-4.5",
"prompt": "A studio product photo of a matte black travel mug on a light gray background",
},
timeout=120,
)
if not response.ok:
raise RuntimeError(f"{response.status_code}: {response.text}")
result = response.json()
images = result.get("data") or []
if not images or not images[0].get("b64_json"):
raise RuntimeError("The response did not contain image data")
image_bytes = base64.b64decode(images[0]["b64_json"])
with open("output.png", "wb") as output_file:
output_file.write(image_bytes)
print("Saved output.png")
cost = result.get("usage", {}).get("cost")
if cost is not None:
print(f"Request cost: ${cost}")
base64.b64decode converts the response string into the original binary image data. Opening the destination with wb prevents Python from treating those bytes as text.
JavaScript
Save this as generate.mjs:
import { writeFile } from "node:fs/promises";
const response = await fetch("https://openrouter.ai/api/v1/images", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "bytedance-seed/seedream-4.5",
prompt: "A studio product photo of a matte black travel mug on a light gray background",
}),
});
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`);
}
const result = await response.json();
if (!result.data?.[0]?.b64_json) {
throw new Error("The response did not contain image data");
}
await writeFile("output.png", Buffer.from(result.data[0].b64_json, "base64"));
console.log("Saved output.png");
if (result.usage?.cost !== undefined) {
console.log(`Request cost: $${result.usage.cost}`);
}
Buffer.from(..., "base64") performs the same conversion, while writeFile saves the resulting bytes.
Run either version from the directory containing the file:
python3 generate.py
For JavaScript use:
node generate.mjs
A successful run prints Saved output.png. The cost line appears only when the response includes usage.cost.
The output format varies by model. Some models return JPEG or WebP bytes instead of PNG. If the format matters for your use, read media_type from the response and pick the file extension to match.
Step 4: Add a reference image
A reference image gives the model visual material to work from instead of relying on the prompt alone. It’s added through input_references.
For a local file, read the bytes, encode them as base64, and prepend the correct media type to create a data URL.
Place the image file product.jpg in the same project folder as the script, then create the file reference.py:
import base64
import os
import requests
with open("product.jpg", "rb") as reference_file:
reference_base64 = base64.b64encode(reference_file.read()).decode("utf-8")
reference_data_url = f"data:image/jpeg;base64,{reference_base64}"
response = requests.post(
"https://openrouter.ai/api/v1/images",
headers={
"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "openai/gpt-image-1",
"prompt": (
"Keep the product shape and materials. Place it on a warm stone "
"surface with soft morning light and a clean commercial style."
),
"input_references": [
{
"type": "image_url",
"image_url": {
"url": reference_data_url,
},
}
],
},
timeout=120,
)
if not response.ok:
raise RuntimeError(f"{response.status_code}: {response.text}")
result = response.json()
images = result.get("data") or []
if not images or not images[0].get("b64_json"):
raise RuntimeError("The response did not contain image data")
variation_bytes = base64.b64decode(images[0]["b64_json"])
with open("variation.png", "wb") as output_file:
output_file.write(variation_bytes)
print("Saved variation.png")
Run it from the same directory:
python3 reference.py
This pattern works well for product-photo variations because the source image can preserve the recognizable object while the prompt changes the setting, lighting, or presentation.
Reference-image support and accepted reference counts vary by model endpoint. Before you depend on this feature, inspect the endpoint record and confirm that input_references appears in supported_parameters.
Step 5: Make the request reusable
Once the request works reliably, move the settings that rarely change into a local configuration file. Keep the model slug, provider routing, timeout, and output directory together. Leave the prompt, reference image, and user controls in the request so they can change with each image.
Troubleshooting and cost notes
Most failures become obvious when you log the HTTP status and full response body before reading any image fields.
- Missing data[0].b64_json. Check the response body first. Confirm that you sent a POST request to /api/v1/images and selected an image-capable model.
- A 401 response. Make sure the process can read OPENROUTER_API_KEY. Check that the variable exists without printing its value, then export it from the terminal running the script.
- A reference request fails. Confirm that the model supports input_references and check the image URL. A local JPEG must use a valid data URL starting with data:image/jpeg;base64,.
- Unexpected cost. Check the endpoint’s pricing before running a batch. If available, record usage.cost with the model slug and output filename during your test runs.
Frequently asked questions
Can I use OpenRouter to generate images?
Yes. Send a POST request to /api/v1/images with an image-capable model, a prompt, and your OpenRouter API key. The response contains base64 image data that you decode and save locally.
How do I use the API to generate images?
Authorize the request with a Bearer header, then send the model and prompt. Check the response status, decode data[0].b64_json, and write the resulting bytes to an image file.
How do I generate AI images through prompts?
Describe the prompt subject, setting, composition, lighting, and style. Use input_references only when a compatible model should edit or vary an existing image.
Which API is best for image generation?
Compare image quality, controls, reference-image support, latency, and pricing. OpenRouter is the best fit when you want one API key and a common request format for multiple image models.
References
- Image generation docs, the canonical reference for POST /api/v1/images and input_references.
- Image models collection, curated image-capable models.
- Live image models API, runtime discovery of slugs and supported parameters.
图像模型的接入难点通常不在第一张图片生成出来之前,而在产品需要同时支持多个供应商之后:endpoint、参数名、返回格式、计费字段和参考图能力各不相同。OpenRouter 在 8 月 17 日发布的图像生成教程,展示了如何用单一 API 入口把这些差异收敛到代码层可以理解的流程。
从模型发现到图片落盘
官方教程将入口定义为 POST https://openrouter.ai/api/v1/images,以 model 和 prompt 作为最小字段。调用返回的 data[0].b64_json 是 Base64 编码的图像数据,客户端需要解码后保存为本地文件;响应还可以带有 media_type 与 usage.cost 等信息。
在选型阶段,GET /api/v1/images/models 用于发现支持图像生成的模型及其参数。教程以 Seedream 为例,但同时提醒分辨率、多图输出和参考图等能力取决于具体模型,不能把统一入口误解成能力完全相同。
参考图参数让工作流更接近产品
除了文字提示,接口还支持可选的 input_references。这让“基于一张已有图片生成变体”的流程可以保持在同一个调用模型里。对产品团队而言,统一的请求和响应格式可以减少业务代码中的供应商分支,把模型选择留给路由层和配置层。
- 图像生成入口为 OpenAI 风格的 POST 请求,基础字段是 model 与 prompt。
- 响应图像放在 data[0].b64_json,客户端需要自行 Base64 解码。
- 模型发现接口用于确认可用模型和参数,具体能力仍以模型记录为准。
- input_references 可用于参考图变体,成本信息可随响应返回。
TopoReduce 编辑观察
统一图像 API 的价值不只是少写几行 SDK 适配代码,更在于让模型发现、成本记录、失败回退和供应商切换有了稳定的系统边界。上层应用可以围绕“生成一张海报”描述业务意图,路由层再根据分辨率、延迟、价格和风格需求选择模型。
对于中转站来说,模型能力清单和价格字段必须保持同步;对于调用方来说,仍应在上线前验证图片尺寸、参考图限制和内容策略,而不是只依赖统一 endpoint。