BlockRun

Image Editing API (img2img)

Edit existing images — pass one or more source images and describe what to change.

Two request formats are accepted
  • application/jsonimage as a data:image/...;base64,… string, or a JSON array of them for multi-image fusion. (BlockRun-native.)
  • multipart/form-data — OpenAI's format: repeated image[]=@file fields (a single image=@file also works), plus mask, model, prompt, size, n. Files are converted internally to data URIs; a file without an image/* MIME type is treated as PNG.

Payment is always x402 (the X-Payment / PAYMENT-SIGNATURE header), regardless of body format. So you can send an OpenAI-shaped multipart body, but you still attach an x402 payment — the OpenAI SDK's native Bearer-key auth does not settle payment here. Use the BlockRun SDK / ClawRouter (which handle x402), or send either body format with your own x402 header.

Endpoint

NetworkURL
Basehttps://blockrun.ai/api/v1/images/image2image
Solanahttps://sol.blockrun.ai/api/v1/images/image2image
POST https://blockrun.ai/api/v1/images/image2image       # Base
POST https://sol.blockrun.ai/api/v1/images/image2image    # Solana
GET  https://blockrun.ai/api/v1/images/generations/{id}  # poll (async slow path only — edits share the generation job store)

Request

{
  "model": "openai/gpt-image-1",
  "prompt": "change the background to a starry sky",
  "image": "data:image/png;base64,<base64-data>",
  "mask": "data:image/png;base64,<base64-data>",
  "size": "1024x1024",
  "n": 1
}

Parameters

ParameterTypeRequiredDescription
modelstringNoModel to use (default: openai/gpt-image-2). Supported: openai/gpt-image-1, openai/gpt-image-2, google/nano-banana, google/nano-banana-2, google/nano-banana-pro. Any other ID (including generation-only models such as bytedance/seedream-5-pro) returns 400 listing the supported set.
promptstringYesDescription of edits to make
imagestring | string[]YesSource image as a base64 data URI, or an array of data URIs to fuse multiple sources into one variant (e.g. a reference image + a brand logo). OpenAI-compatible image[]. Max 4 images for OpenAI, 3 for Google. See Accepted image formats.
maskstringNoMask as a base64 data URI, validated like image. OpenAI models only. Single-image only — cannot be combined with a multi-image image[] array.
sizestringNoOutput size (default: 1024x1024). Must be one of the model's listed sizes, otherwise 400.
nintegerNoNumber of images to generate (default: 1, min 1, max 10). Validated before the 402.

Unknown fields are ignored. A body that is not valid JSON (or an unparsable multipart form) returns 400; a body that fails validation returns 400 { "error": "Invalid request body", "details": [...] }.

Accepted image formats

Every image and mask value is decoded and sniffed before the 402, so bad input is a 400 and never costs a signed round-trip. To pass the gate a data URI must:

  • start with data:image/, and
  • decode to real image bytes — PNG, JPEG, GIF, BMP, WEBP, or an ISO-BMFF container (HEIC / HEIF / AVIF). A syntactically valid URI with garbage bytes (e.g. data:image/png;base64,AAAA) is rejected with image data URI does not decode to a recognizable image (expected PNG, JPEG, GIF, BMP, WEBP, or HEIC/HEIF/AVIF).

The gate only rejects undecodable data; the model is the final authority on formats. Google Nano Banana models accept HEIC/HEIF (the iPhone camera default), while OpenAI GPT Image models do not — sending one there returns 400 { "error": "Invalid input image", "details": "..." } from the model's own check, still without a charge. There is no separate byte cap on the source images beyond what fits in one HTTP request.

Supported Models

Prices are what the 402 challenge quotes and what is billed for one edit at that size — catalog rate + 5% + the flat $0.001 transaction fee (see Pricing).

Model IDProviderMaskMax source imagesSizesPrice (n=1)
openai/gpt-image-1OpenAI41024x1024, 1536x1024, 1024x1536$0.022 / $0.043
openai/gpt-image-2OpenAI41024x1024, 1536x1024, 1024x1536$0.064 / $0.127
google/nano-bananaGoogle (Gemini 2.5 Flash Image)❌ prompt-only31024x1024$0.0535
google/nano-banana-2Google (Gemini 3.1 Flash Image)❌ prompt-only31024x1024$0.0955
google/nano-banana-proGoogle (Gemini 3 Pro Image)❌ prompt-only31024x1024, 2048x2048, 4096x4096$0.106 / $0.106 / $0.1585

Hybrid sync/async flow & settlement

Like /v1/images/generations, this endpoint is hybrid — it is not always synchronous:

  • Fast edits (≤30s inline window): 200 with the standard { id, created, data: [...] } body below. Payment settles inside this response.
  • Slow edits (>30s — openai/gpt-image-2 single-image edits routinely take 45–90s, and multi-image fusion longer; its upstream call is allowed up to 600s): 202 with an async job envelope { id, object: "image.edit.job", status, poll_url, price, payment_status: "verified" }. Nothing has been charged yet. Poll GET {poll_url} (it lives under /v1/images/generations/{id} — edits share the same job store) with an x-payment header from the same wallet every 2–5s. USDC settles exactly once, on the first poll that observes status: "completed". A job that ends failed, or that you never poll, is never charged.
No charge until the image is ready

On the slow async path nothing is debited at submit time (payment_status: "verified"). USDC settles exactly once — on the first poll that sees status: "completed". Failed jobs and never-polled jobs are never charged. The signed authorization is valid for 600s (maxTimeoutSeconds in the challenge), so finish polling within 10 minutes of signing.

The envelope fields, poll responses, status enum (queued → in_progress → completed | failed), replay recovery (402 PAYMENT_REPLAY with job_id + poll_url) and settlement guarantees are identical to image generation — see the full spec there. Go SDK blockrun-llm-go ≥ v0.17.0 (ImageClient.Edit) handles the hybrid flow transparently and always returns the synchronous shape.

Response

{
  "id": "img_8f3a…",
  "created": 1706000000,
  "data": [
    {
      "url": "data:image/png;base64,…",
      "revised_prompt": "A photo with the background changed to..."
    }
  ],
  "price": { "amount": "0.064000", "currency": "USD" },
  "payment": { "status": "settled", "tx_hash": "0x…", "network": "base" }
}

Headers: PAYMENT-RESPONSE (x402 settlement receipt) and X-Payment-Receipt (the on-chain tx hash).

Response Fields

FieldTypeDescription
idstringPer-call job id (same value as job_id in the async envelope, poll responses and replay 402s)
createdintegerUnix timestamp
dataarrayArray of edited images
data[].urlstringFast path (200 from the POST): the model's raw output, which for every supported model is a data:image/...;base64, URI — it is not mirrored to durable storage, so persist it yourself. Slow path (200 from the poll): a permanent BlockRun-hosted proxy URL, with source_url and backed_up alongside, exactly like image generation.
data[].revised_promptstringExpanded prompt (when the model returns one)
price.amountstringUSD billed for this call (includes the 5% margin and the $0.001 transaction fee)
payment.statusstringsettled | already_settled
payment.tx_hashstringOn-chain USDC settlement tx (also in X-Payment-Receipt header)

Examples

Via ClawRouter (recommended)

The easiest way is using ClawRouter's /img2img slash command, which reads local files automatically:

/img2img --image ~/photo.png change the background to a starry sky
/img2img --image ./cat.jpg --mask ./mask.png remove the background
/img2img --image /tmp/portrait.png --size 1536x1024 add a hat

Via ClawRouter API

ClawRouter handles x402 payments automatically:

# First, convert your image to base64
IMAGE_B64=$(base64 -i ~/photo.png)

curl -X POST http://localhost:8402/v1/images/image2image \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"openai/gpt-image-1\",
    \"prompt\": \"change the background to a starry sky\",
    \"image\": \"data:image/png;base64,${IMAGE_B64}\",
    \"size\": \"1024x1024\"
  }"

Direct API (cURL)

IMAGE_B64=$(base64 -i ~/photo.png)

# Base
curl -X POST https://blockrun.ai/api/v1/images/image2image \
  -H "Content-Type: application/json" \
  -H "X-Payment: $PAYMENT_HEADER" \
  -d "{
    \"model\": \"openai/gpt-image-1\",
    \"prompt\": \"change the background to a starry sky\",
    \"image\": \"data:image/png;base64,${IMAGE_B64}\",
    \"size\": \"1024x1024\"
  }"

# Solana
curl -X POST https://sol.blockrun.ai/api/v1/images/image2image \
  -H "Content-Type: application/json" \
  -H "X-Payment: $PAYMENT_HEADER" \
  -d "{
    \"model\": \"openai/gpt-image-1\",
    \"prompt\": \"change the background to a starry sky\",
    \"image\": \"data:image/png;base64,${IMAGE_B64}\",
    \"size\": \"1024x1024\"
  }"

Mask Usage

Mask is OpenAI-only

Masks apply to openai/gpt-image-1 and openai/gpt-image-2. Google nano-banana / nano-banana-2 / nano-banana-pro are prompt-only — passing a mask to a Google model returns 400 before payment.

The mask is passed to the model unchanged, so it follows the OpenAI Images edit convention: a PNG with an alpha channel, the same dimensions as the source image.

  • Fully transparent pixels (alpha 0) = areas to edit (the model generates new content here)
  • Opaque pixels = areas to keep unchanged

If no mask is provided, the model edits the entire image based on the prompt. The mask goes through the same pre-402 byte check as image.

Multi-image input (image fusion)

Pass an array of source images in image to fuse several inputs into one result — e.g. a reference image + a brand logo, composed by a single prompt.

  • OpenAI gpt-image-1 / gpt-image-2: up to 4 source images.
  • Google nano-banana / nano-banana-2 / nano-banana-pro: up to 3 source images (earlier images act as primary composition anchors).
  • mask cannot be combined with a multi-image array (mask is single-region only) → 400.
  • Exceeding a model's image cap → 400 (Model … accepts at most N source images per edit).
Two ways to send multiple images
  • JSON (shown below): image as a JSON array of base64 data URIs.
  • Multipart (OpenAI's format): repeated image[]=@file fields, e.g. -F "image[]=@ref.png" -F "image[]=@logo.png".

Both are accepted. Payment is still x402 in either case (see the format note at the top).

REF_B64=$(base64 -i ~/reference-post.png)
LOGO_B64=$(base64 -i ~/brand-logo.png)

curl -X POST https://blockrun.ai/api/v1/images/image2image \
  -H "Content-Type: application/json" \
  -H "X-Payment: $PAYMENT_HEADER" \
  -d "{
    \"model\": \"google/nano-banana\",
    \"prompt\": \"place the brand logo in the corner, vintage warm palette, no text overlay\",
    \"image\": [
      \"data:image/png;base64,${REF_B64}\",
      \"data:image/png;base64,${LOGO_B64}\"
    ],
    \"size\": \"1024x1024\"
  }"

Same request in OpenAI multipart format (repeated image[]):

curl -X POST https://blockrun.ai/api/v1/images/image2image \
  -H "X-Payment: $PAYMENT_HEADER" \
  -F "model=google/nano-banana" \
  -F "prompt=place the brand logo in the corner, vintage warm palette, no text overlay" \
  -F "image[]=@reference-post.png" \
  -F "image[]=@brand-logo.png" \
  -F "size=1024x1024"

Single-image requests are unchanged — image as a plain string (JSON) or a single image[]/image file (multipart) both work.

Pricing

Edits bill exactly like generations on the same model:

billed = catalog rate for the size × n × 1.05   (5% platform margin on media)
       + $0.001                                 (flat transaction fee, once per call)
ModelSizeCatalog rateBilled (n=1)
GPT Image 11024x1024$0.02$0.022
GPT Image 11536x1024$0.04$0.043
GPT Image 11024x1536$0.04$0.043
ChatGPT Images 2.01024x1024$0.06$0.064
ChatGPT Images 2.01536x1024$0.12$0.127
ChatGPT Images 2.01024x1536$0.12$0.127
Nano Banana1024x1024$0.05$0.0535
Nano Banana 21024x1024$0.09$0.0955
Nano Banana Pro1024x1024$0.10$0.106
Nano Banana Pro2048x2048$0.10$0.106
Nano Banana Pro4096x4096$0.15$0.1585

The number of source images does not change the price — you pay per output image.

Error Codes

CodeDescription
400Invalid request: malformed JSON / multipart, failed validation (details[] — including an image or mask that does not decode to a real image), unsupported model (message lists the supported set), mask on a Google model, mask combined with multiple images, too many source images, invalid size for the model, n outside 1–10, Invalid input image (the model rejected the file, e.g. HEIC on an OpenAI model), or Content policy violation. None of these are billed.
402Payment required / rejected / replayed. Same shapes and machine-readable codes (PAYMENT_INVALID, PAYMENT_UNFUNDED, PAYMENT_BLOCKHASH_STALE, PAYMENT_REPLAY) as image generation; the unpaid body has no generation_info block.
403 / 404Poll only — payer mismatch / job not found (see the poll responses).
429Upstream rate limit ({ "error": "Rate limit exceeded", "details" }). BlockRun applies no per-IP limit of its own to this endpoint.
500Server error (Image editing failed / Internal server error, with details and the job_id). Settlement never ran, so nothing was billed.
504Image editing timed out — rare: an edit that outlives the 30s inline window normally goes async (202) instead of timing out.

What's next?