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, andPOST /v1/images/editsfor the GPT Image models. - Measured generation:
gpt-image-1-miniatlowquality, 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 withnano-banana-2at 0.5K (background replaced, bag faithful, price readable). - Reference images are sent as base64 in the
imagesfield. GPT Image accepts 16, Nano Banana 14. - A GPT Image edit goes through
/images/edits. Withgpt-image-1-mini, the same request sent to/images/generationsreturned a502and debited nothing.
Which model for what
Not every model takes a reference image. On the ones I looked at:
| Model | Generation | Reference image | Price settings |
|---|---|---|---|
gpt-image-1-mini | yes | yes (up to 16) | quality: low, medium, high |
nano-banana-2 | yes | yes (up to 14) | resolution: 0.5K, 1K, 2K, 4K |
flux-2-pro | yes | no | one 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):
| Model | Setting | Credits | In KES |
|---|---|---|---|
gpt-image-1-mini | low | 11 | about KES 0.83 |
gpt-image-1-mini | medium | 24 | about KES 1.82 |
gpt-image-1-mini | high | 77 | about KES 5.83 |
nano-banana-2 | 0.5K | 101.28 | about KES 7.67 |
nano-banana-2 | 1K | 142 | about KES 10.75 |
nano-banana-2 | 2K | 214 | about 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,nis brought down to 1.sizefor GPT Image:1024x1024,1024x1536or1536x1024. Any other value returns400.qualityfor GPT Image:low,medium,high. If you forget it,highis billed, so the most expensive: remember to set it.resolutionfor Nano Banana:0.5K,1K,2K,4K, andaspect_ratiofor framing (for example1:1,4:3,9:16).response_format:b64_jsonby default, orurldepending on the model.
Common errors and what they mean
I triggered these errors on purpose. None of them debited credits.
| What I sent | Response | What it means |
|---|---|---|
size: "512x512" on gpt-image-1-mini | 400 bad_request | The message lists the accepted sizes |
quality: "ultra" | 400 bad_request | The message lists low, medium, high |
resolution: "8K" on nano-banana-2 | 400 bad_request | The message lists 0.5K, 1K, 2K, 4K |
aspect_ratio: "5:1" on nano-banana-2 | 400 bad_request | The message lists the accepted ratios |
images on flux-2-pro | 400 bad_request | This model does not accept a reference image |
image and images together | 400 bad_request | Send one or the other |
| A model that does not exist | 404 model_not_found | Check the identifier with GET /v1/models |
images on /images/generations with gpt-image-1-mini | 502 provider_error | Use /images/edits for GPT Image models |
Body without model or without prompt | 502 provider_error | Validate 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.