Guides

AI Image Generation and Editing API for Product Photos

TchaviBy Gael — Tchavi founder

Published · Updated · 10 min read

Back to blog

Table of contents

For a shop on Jumia, Kilimall or Instagram, two calls are enough: one generates the product photo from a text, the other edits it from that photo (white background, price tag). On my runs, a generation cost 11 credits and an edit between 17 and 96 credits, in 8 to 9 seconds.

I made the three calls for real, in October 2026, through Tchavi, with an imaginary wax-fabric handbag. I wrote the prompts in French, so the prompt strings in the code stay in French, and the measured results are exactly as I got them. Here is the code in Node.js and Python, what it cost, and what each model did well or badly.

In short

  • Tchavi, an OpenAI-compatible AI API gateway you pay for with mobile money, generates and edits images with the same key as text. The route is POST /v1/images/generations, and POST /v1/images/edits for the GPT Image models.
  • Measured generation: gpt-image-1-mini at low quality, 1024x1024, cost 11 credits (about KES 0.83 at October 2026 rates) in 8.5 seconds.
  • Measured edit: 17 credits with gpt-image-1-mini (background replaced, but the price text was wrong), 96 credits with nano-banana-2 at 0.5K (background replaced, bag faithful, price readable).
  • Reference images are sent as base64 in the images field. GPT Image accepts 16, Nano Banana 14.
  • A GPT Image edit goes through /images/edits. With gpt-image-1-mini, the same request sent to /images/generations returned a 502 and debited nothing.

Which model for what

Not every model takes a reference image. On the ones I looked at:

ModelGenerationReference imagePrice settings
gpt-image-1-miniyesyes (up to 16)quality: low, medium, high
nano-banana-2yesyes (up to 14)resolution: 0.5K, 1K, 2K, 4K
flux-2-proyesnoone price per image

For flux-2-pro, I sent a reference image and the API answered 400 bad_request: image input is reserved for certain model families. The image documentation lists the families, and each model's API tab gives its exact parameters.

Credit prices for a square image, from the catalogue, with KES at October 2026 rates (about 13,204 credits per KES 1,000, so 1 credit is about KES 0.0757):

ModelSettingCreditsIn KES
gpt-image-1-minilow11about KES 0.83
gpt-image-1-minimedium24about KES 1.82
gpt-image-1-minihigh77about KES 5.83
nano-banana-20.5K101.28about KES 7.67
nano-banana-21K142about KES 10.75
nano-banana-22K214about KES 16.20

Model pages: gpt-image-1-mini and nano-banana-2. My advice: start at the lowest-priced setting, look at the result, and only go up if the image doesn't hold up.

The image format changes the price too. A non-square size is billed 1.5 times the square price on gpt-image-1-mini: I measured 16.5 credits for a 1024x1536 at low, against 11 for square. For a vertical Instagram visual, that is the price to plan for.

Step 1: generate the product photo (Node.js)

import { writeFileSync } from 'node:fs';

const BASE = 'https://tchavi.com/api/v1';
const headers = {
  'Content-Type': 'application/json',
  Authorization: `Bearer ${process.env.TCHAVI_API_KEY}`,
};

const res = await fetch(`${BASE}/images/generations`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    model: 'gpt-image-1-mini',
    quality: 'low',
    size: '1024x1024',
    prompt:
      'Photo produit pour une boutique en ligne : un sac à main en tissu wax coloré (motifs jaune, bleu et rouge), posé sur une table en bois, lumière douce de fenêtre, fond de salon flou, rendu réaliste',
  }),
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error}: ${json.message}`);

const image = json.data[0];
const ext = image.media_type.split('/')[1]; // png, jpeg or webp
writeFileSync(`bag.${ext}`, Buffer.from(image.b64_json, 'base64'));
console.log('credits:', res.headers.get('x-credits-used'));

The image is in data[0].b64_json, its type in data[0].media_type. Read the type instead of assuming PNG: depending on the model, you can receive PNG, JPEG or WebP.

Result of my call: status 200, 8.5 seconds, 11 credits, a 1.5 MB PNG. The bag is legible, with plausible wax patterns, but the material looks more like quilted fabric than wax. That is the level of detail of a low setting.

Step 2: edit from a reference image

This is the useful part for a seller: keep the product and change the setting. The request is the same, with an images field that holds your reference images in base64.

With GPT Image: the /images/edits route

import { readFileSync, writeFileSync } from 'node:fs';

const reference = readFileSync('bag.png').toString('base64');

const res = await fetch(`${BASE}/images/edits`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    model: 'gpt-image-1-mini',
    quality: 'low',
    size: '1024x1024',
    images: [reference],
    prompt:
      'Garde exactement le même sac. Remplace le fond par un fond blanc uni de studio, et ajoute une étiquette de prix ronde jaune avec le texte « 12 500 F » en haut à droite.',
  }),
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error}: ${json.message}`);
writeFileSync('bag-edited.png', Buffer.from(json.data[0].b64_json, 'base64'));

Measured: 200, 8.9 seconds, 17 credits (about KES 1.29). That is the 11 credits of the generation, plus 6 credits for the reference image: on gpt-image-1-mini, each reference image is billed on top of the image price.

I only tested gpt-image-1-mini, so I say nothing about the other GPT Image models, or about /images/edits with Nano Banana.

The background did turn white and the yellow tag is there. Two flaws: the bag's pattern was redrawn (it is no longer exactly the same bag), and the tag reads "12300F" instead of "12 500 F". Exact text in an image is the weak point of low settings. For a price tag, I advise you to add it yourself in code (an SVG image or a canvas) rather than ask the model.

With Nano Banana 2: the /images/generations route

For Nano Banana, editing goes through the generation route, with resolution instead of quality:

import base64
import os

import requests

BASE = "https://tchavi.com/api/v1"
reference = base64.b64encode(open("bag.png", "rb").read()).decode()

r = requests.post(
    f"{BASE}/images/generations",
    headers={"Authorization": f"Bearer {os.environ['TCHAVI_API_KEY']}"},
    json={
        "model": "nano-banana-2",
        "resolution": "0.5K",
        "images": [reference],
        "prompt": "Garde exactement le même sac. Remplace le fond par un fond blanc uni de studio, "
                  "et ajoute une étiquette de prix ronde jaune avec le texte « 12 500 F » en haut à droite.",
    },
    timeout=120,
)
data = r.json()
if not r.ok:
    raise RuntimeError(f"{data['error']}: {data['message']}")

image = data["data"][0]
ext = image["media_type"].split("/")[1]
open(f"bag-nano.{ext}", "wb").write(base64.b64decode(image["b64_json"]))
print("credits:", r.headers.get("x-credits-used"))

Measured: 200, 8.3 seconds, 96 credits (about KES 7.27), which was the catalogue price at 0.5K that day (101.28 credits at the current rate), with no visible extra for the reference image, and an image 512 pixels on a side (that is what 0.5K means). Here the result is clearly better: the bag is faithful to the original, the background is white, and the tag does say "12 500 F".

What this tells me, from a single run per model: at these settings, the 96-credit edit holds the product and the text better than the 17-credit one. It costs almost six times more. It is up to you to decide whether your catalogue justifies the difference. One run is not a statistic: repeat the test with your own photos before choosing.

The settings that matter

  • n: the number of images, from 1 to 4. Nano Banana models return one image per call, n is brought down to 1.
  • size for GPT Image: 1024x1024, 1024x1536 or 1536x1024. Any other value returns 400.
  • quality for GPT Image: low, medium, high. If you forget it, high is billed, so the most expensive: remember to set it.
  • resolution for Nano Banana: 0.5K, 1K, 2K, 4K, and aspect_ratio for framing (for example 1:1, 4:3, 9:16).
  • response_format: b64_json by default, or url depending on the model.

Common errors and what they mean

I triggered these errors on purpose. None of them debited credits.

What I sentResponseWhat it means
size: "512x512" on gpt-image-1-mini400 bad_requestThe message lists the accepted sizes
quality: "ultra"400 bad_requestThe message lists low, medium, high
resolution: "8K" on nano-banana-2400 bad_requestThe message lists 0.5K, 1K, 2K, 4K
aspect_ratio: "5:1" on nano-banana-2400 bad_requestThe message lists the accepted ratios
images on flux-2-pro400 bad_requestThis model does not accept a reference image
image and images together400 bad_requestSend one or the other
A model that does not exist404 model_not_foundCheck the identifier with GET /v1/models
images on /images/generations with gpt-image-1-mini502 provider_errorUse /images/edits for GPT Image models
Body without model or without prompt502 provider_errorValidate your request in code: the API does not say it clearly

The other codes come from the errors page: 401 invalid_api_key (wrong key), 402 insufficient_credits (balance too low, with credits_remaining and credits_required in details), 429 rate_limit_exceeded (wait Retry-After seconds). If you get a 429, a retry loop with a pause fixes it.

A 502 does not always mean "outage": in these last two cases, it was a malformed request. Before you retry in a loop, re-read your request body.

A ready-made loop for a catalogue

If you have twenty products to put on a white background, call the route in series with a pause on 429, and keep the total cost:

async function editProduct(file) {
  const images = [readFileSync(file).toString('base64')];
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(`${BASE}/images/edits`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        model: 'gpt-image-1-mini',
        quality: 'low',
        images,
        prompt: 'Garde le même produit, fond blanc uni de studio.',
      }),
    });
    if (res.status === 429) {
      const { details } = await res.json();
      await new Promise((r) => setTimeout(r, (details?.retry_after ?? 2) * 1000));
      continue;
    }
    const json = await res.json();
    if (!res.ok) throw new Error(`${json.error}: ${json.message}`);
    return { image: json.data[0], credits: Number(res.headers.get('x-credits-used')) };
  }
  throw new Error('too many 429s');
}

At 17 credits per edit, twenty products come to 340 credits, about KES 26. That is a calculation from my measurement, not a measurement on twenty images.

FAQ

How much does an image generated by the API cost?

On gpt-image-1-mini, 11 credits at low quality, 24 at medium and 77 at high, for a square image. At October 2026 rates, that is about KES 0.83 for the simplest. The real cost of each call is in the x-credits-used header.

Which models accept a reference image?

gpt-image-1-mini (up to 16 images) and nano-banana-2 (up to 14) accept it, from my runs and the documentation. flux-2-pro refuses it with a 400. For another model, look at its API tab.

Why doesn't my edited image look like the product any more?

The model redraws the image from your reference: it does not copy it. The lower the setting, the more it invents. Say "keep exactly the same product", try a higher setting, and run several attempts before you publish.

How do I get exact text on the image, like a price?

Don't count on it. My 17-credit run wrote "12300F" instead of "12 500 F". Add the price in code afterwards, it is free and exact.

Which image format do I receive?

Read data[0].media_type: image/png, image/jpeg or image/webp. My three images were PNGs, from 0.4 to 1.5 MB, so compress them before you put them on your shop.

Should I send the image as base64 or as a URL?

For the reference, I used raw base64 in images. The field also accepts a data:image/png;base64,... string.

How do I pay without a bank card?

With mobile money: the minimum top-up is KES 250, and there is no subscription and no free credits at signup. Your first paid top-up adds 150 bonus credits. See countries and operators, including M-Pesa in Kenya.

create your account and top up KES 250

This article is part of the series "Building an AI product from Africa". Tchavi is an OpenAI-compatible AI API gateway: one key gives access to over 100 text, image, audio and video models, and you pay for it with mobile money, no bank card (countries and operators). More guides on the blog. Follow us: LinkedIn and X.

Share

Your first API call, paid with mobile money

No bank card. Top up with mobile money in your local currency; your first top-up adds 150 bonus credits, and credits never expire.