# Générer et retoucher des images produit par l'API (Node, Python)

> Génère une photo produit par l'API, puis retouche-la depuis une image de référence (fond blanc, prix). Code Node et Python, coûts et erreurs mesurés.

- Published: 2026-10-18
- Updated: 2026-11-02
- Author: Gael — fondateur de Tchavi
- Canonical: https://tchavi.com/fr/blog/api-generation-retouche-images-produit

---


Pour une boutique sur Jumia, Afrikrea ou Instagram, deux appels suffisent : l'un génère la photo du produit à partir d'un texte, l'autre la retouche à partir de cette photo (fond blanc, étiquette de prix). Sur mes essais, une génération a coûté 11 crédits et une retouche entre 17 et 96 crédits, en 8 à 9 secondes.

J'ai fait les trois appels pour de vrai, en octobre 2026, à travers Tchavi, avec un sac en wax imaginaire. Voici le code en Node.js et en Python, ce que ça a coûté, et ce que chaque modèle a bien ou mal fait.

## En bref

- **Tchavi, une passerelle d'API IA compatible OpenAI que tu paies en mobile money, génère et retouche des images avec la même clé que le texte.** La route est `POST /v1/images/generations`, et `POST /v1/images/edits` pour les modèles GPT Image.
- **Génération mesurée** : `gpt-image-1-mini` en qualité `low`, 1024x1024, a coûté 11 crédits (environ 3,7 F) en 8,5 secondes.
- **Retouche mesurée** : 17 crédits avec `gpt-image-1-mini` (fond remplacé, mais le texte du prix était faux), 96 crédits avec `nano-banana-2` en 0.5K (fond remplacé, sac fidèle, prix lisible).
- **Les images de référence sont envoyées en base64** dans le champ `images`. GPT Image en accepte 16, Nano Banana 14.
- **Une retouche GPT Image passe par `/images/edits`.** Avec `gpt-image-1-mini`, la même requête envoyée à `/images/generations` m'a renvoyé un `502` sans rien débiter.

## Quel modèle pour quoi

Tous les modèles ne prennent pas une image de référence. Sur ceux que j'ai regardés :

| Modèle             | Génération | Image de référence | Réglages de prix                        |
| ------------------ | ---------- | ------------------ | --------------------------------------- |
| `gpt-image-1-mini` | oui        | oui (jusqu'à 16)   | `quality` : `low`, `medium`, `high`     |
| `nano-banana-2`    | oui        | oui (jusqu'à 14)   | `resolution` : `0.5K`, `1K`, `2K`, `4K` |
| `flux-2-pro`       | oui        | non                | tarif unique par image                  |

Pour `flux-2-pro`, j'ai envoyé une image de référence et l'API a répondu `400 bad_request` : l'entrée image est réservée à certaines familles de modèles. La [documentation des images](https://tchavi.com/fr/docs/image-generation) donne la liste des familles, et l'onglet API de chaque modèle donne ses paramètres exacts.

Les prix en crédits pour une image carrée, d'après le catalogue (3 crédits = 1 F) :

| Modèle             | Réglage  | Crédits | En F           |
| ------------------ | -------- | ------- | -------------- |
| `gpt-image-1-mini` | `low`    | 11      | environ 3,7 F  |
| `gpt-image-1-mini` | `medium` | 24      | 8 F            |
| `gpt-image-1-mini` | `high`   | 77      | environ 25,7 F |
| `nano-banana-2`    | `0.5K`   | 101,28  | environ 33,8 F |
| `nano-banana-2`    | `1K`     | 142     | environ 47,3 F |
| `nano-banana-2`    | `2K`     | 214     | environ 71,3 F |

Pages des modèles : [`gpt-image-1-mini`](https://tchavi.com/fr/m/openai/gpt-image-1-mini) et [`nano-banana-2`](https://tchavi.com/fr/m/google/nano-banana-2). Le conseil que je te donne : commence au réglage le moins cher, regarde le résultat, et ne monte que si l'image ne tient pas la route.

Le format de l'image change aussi le prix. Un `size` non carré est facturé 1,5 fois le prix carré sur `gpt-image-1-mini` : j'ai mesuré 16,5 crédits pour un 1024x1536 en `low`, contre 11 en carré. Pour un visuel vertical d'Instagram, c'est le prix à prévoir.

## Étape 1 : générer la photo produit (Node.js)

```javascript
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 ou webp
writeFileSync(`sac.${ext}`, Buffer.from(image.b64_json, 'base64'));
console.log('crédits :', res.headers.get('x-credits-used'));
```

L'image est dans `data[0].b64_json`, son type dans `data[0].media_type`. Lis le type au lieu de supposer du PNG : selon le modèle, tu peux recevoir du PNG, du JPEG ou du WebP.

Résultat de mon appel : statut 200, 8,5 secondes, 11 crédits, un PNG de 1,5 Mo. Le sac est lisible, avec des motifs wax plausibles, mais la matière ressemble plus à du tissu matelassé qu'à du wax. C'est le niveau de détail d'un réglage `low`.

## Étape 2 : retoucher depuis une image de référence

Voilà la partie utile pour un vendeur : garder le produit et changer le décor. La requête est la même, avec un champ `images` qui contient tes images de référence en base64.

### Avec GPT Image : la route `/images/edits`

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

const reference = readFileSync('sac.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('sac-retouche.png', Buffer.from(json.data[0].b64_json, 'base64'));
```

Mesuré : 200, 8,9 secondes, 17 crédits (environ 5,7 F). Ce sont les 11 crédits de la génération, plus 6 crédits pour l'image de référence : sur `gpt-image-1-mini`, chaque image de référence est facturée en plus du prix de l'image.

Je n'ai testé que `gpt-image-1-mini` : je ne dis donc rien des autres modèles GPT Image, ni de `/images/edits` avec Nano Banana.

Le fond est bien devenu blanc et l'étiquette jaune est là. Deux défauts : le motif du sac a été redessiné (ce n'est plus exactement le même sac), et l'étiquette affiche « 12300F » au lieu de « 12 500 F ». Un texte exact dans une image est le point faible des réglages bas. Pour une étiquette de prix, je te conseille de l'ajouter toi-même en code (une image SVG ou un canevas) plutôt que de le demander au modèle.

### Avec Nano Banana 2 : la route `/images/generations`

Pour Nano Banana, la retouche passe par la route de génération, avec `resolution` au lieu de `quality` :

```python
import base64
import os

import requests

BASE = "https://tchavi.com/api/v1"
reference = base64.b64encode(open("sac.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"sac-nano.{ext}", "wb").write(base64.b64decode(image["b64_json"]))
print("crédits :", r.headers.get("x-credits-used"))
```

Mesuré : 200, 8,3 secondes, 96 crédits (32 F), soit le prix du catalogue en `0.5K` ce jour-là (101,28 crédits au tarif actuel), sans supplément visible pour l'image de référence, une image de 512 pixels de côté (c'est ce que veut dire `0.5K`). Ici, le résultat est nettement meilleur : le sac est fidèle à l'original, le fond est blanc, et l'étiquette dit bien « 12 500 F ».

Ce que ça m'apprend, sur un seul essai par modèle : à ces réglages, la retouche à 96 crédits tient mieux le produit et le texte que celle à 17 crédits. Ça coûte presque six fois plus. À toi de voir si ton catalogue justifie la différence. Un essai ne fait pas une statistique : refais le test avec tes propres photos avant de choisir.

## Les réglages qui comptent

- **`n`** : le nombre d'images, de 1 à 4. Les modèles Nano Banana renvoient une image par appel, `n` est ramené à 1.
- **`size`** pour GPT Image : `1024x1024`, `1024x1536` ou `1536x1024`. Une autre valeur renvoie `400`.
- **`quality`** pour GPT Image : `low`, `medium`, `high`. Si tu l'oublies, `high` est facturé, donc le plus cher : pense à le fixer.
- **`resolution`** pour Nano Banana : `0.5K`, `1K`, `2K`, `4K`, et `aspect_ratio` pour le cadrage (par exemple `1:1`, `4:3`, `9:16`).
- **`response_format`** : `b64_json` par défaut, ou `url` selon le modèle.

## Les erreurs courantes et ce qu'elles veulent dire

J'ai provoqué ces erreurs exprès. Aucune n'a débité de crédits.

| Ce que j'ai envoyé                                         | Réponse               | Ce que ça veut dire                                          |
| ---------------------------------------------------------- | --------------------- | ------------------------------------------------------------ |
| `size: "512x512"` sur `gpt-image-1-mini`                   | `400 bad_request`     | Le message liste les tailles acceptées                       |
| `quality: "ultra"`                                         | `400 bad_request`     | Le message liste `low`, `medium`, `high`                     |
| `resolution: "8K"` sur `nano-banana-2`                     | `400 bad_request`     | Le message liste `0.5K`, `1K`, `2K`, `4K`                    |
| `aspect_ratio: "5:1"` sur `nano-banana-2`                  | `400 bad_request`     | Le message liste les rapports acceptés                       |
| `images` sur `flux-2-pro`                                  | `400 bad_request`     | Ce modèle n'accepte pas d'image de référence                 |
| `image` et `images` ensemble                               | `400 bad_request`     | Envoie l'un ou l'autre                                       |
| Un modèle qui n'existe pas                                 | `404 model_not_found` | Vérifie l'identifiant avec `GET /v1/models`                  |
| `images` sur `/images/generations` avec `gpt-image-1-mini` | `502 provider_error`  | Utilise `/images/edits` pour les modèles GPT Image           |
| Corps sans `model` ou sans `prompt`                        | `502 provider_error`  | Valide ta requête côté code : l'API ne le dit pas clairement |

Les autres codes viennent de la [page des erreurs](https://tchavi.com/fr/docs/errors) : `401 invalid_api_key` (clé fausse), `402 insufficient_credits` (solde trop bas, avec `credits_remaining` et `credits_required` dans `details`), `429 rate_limit_exceeded` (attends `Retry-After` secondes). Si tu reçois un `429`, une boucle de réessai avec une pause règle ça.

Un `502` ne veut pas toujours dire « panne » : sur ces deux derniers cas, c'était une requête mal formée. Avant de réessayer en boucle, relis ton corps de requête.

## Une boucle prête à l'emploi pour un catalogue

Si tu as vingt produits à mettre sur fond blanc, appelle la route en série avec une pause sur `429`, et garde le coût total :

```javascript
async function retoucher(fichier) {
  const images = [readFileSync(fichier).toString('base64')];
  for (let essai = 0; essai < 5; essai++) {
    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('trop de 429');
}
```

À 17 crédits la retouche, vingt produits font 340 crédits, soit environ 113 F. C'est un calcul à partir de ma mesure, pas une mesure sur vingt images.

## FAQ

### Combien coûte une image générée par l'API ?

Sur `gpt-image-1-mini`, 11 crédits en qualité `low`, 24 en `medium` et 77 en `high`, pour une image carrée. Avec 3 crédits pour 1 F, c'est environ 3,7 F pour la plus simple. Le coût réel de chaque appel est dans l'en-tête `x-credits-used`.

### Quels modèles acceptent une image de référence ?

`gpt-image-1-mini` (jusqu'à 16 images) et `nano-banana-2` (jusqu'à 14) l'acceptent, d'après mes essais et la documentation. `flux-2-pro` la refuse avec un `400`. Pour un autre modèle, regarde son onglet API.

### Pourquoi mon image retouchée ne ressemble plus au produit ?

Le modèle redessine l'image à partir de ta référence : il ne la copie pas. Plus le réglage est bas, plus il invente. Précise « garde exactement le même produit », essaie un réglage supérieur, et teste plusieurs essais avant de publier.

### Comment avoir un texte exact sur l'image, comme un prix ?

Ne compte pas dessus. Mon essai à 17 crédits a écrit « 12300F » au lieu de « 12 500 F ». Ajoute le prix par code après coup, c'est gratuit et exact.

### Quel format d'image je reçois ?

Lis `data[0].media_type` : `image/png`, `image/jpeg` ou `image/webp`. Mes trois images étaient des PNG, de 0,4 à 1,5 Mo, donc compresse-les avant de les mettre sur ta boutique.

### Dois-je envoyer l'image en base64 ou en URL ?

Pour la référence, j'ai utilisé du base64 brut dans `images`. Le champ accepte aussi une chaîne `data:image/png;base64,...`.

### Comment payer sans carte bancaire ?

En mobile money : tu recharges à partir de 1 000 F, 3 crédits valent 1 F, et il n'y a ni abonnement ni crédits gratuits à l'inscription. Ta première recharge ajoute 150 crédits de bonus.

[crée ton compte et recharge 1 000 F](https://tchavi.com/fr/register?next=%2Fdashboard%2Fbilling%3Famount%3D1000%26currency%3DXOF%26from%3Dblog-images-api)

> _Cet article fait partie de la série « Construire un SaaS IA depuis l'Afrique francophone ». Tchavi est une passerelle d'API IA compatible OpenAI : une seule clé donne accès à plus de 100 modèles de texte, d'image, d'audio et de vidéo, et tu la paies en mobile money, sans carte bancaire ([pays et opérateurs](https://tchavi.com/fr/docs/payments)). Plus de guides sur [le blog](https://tchavi.com/fr/blog). Pour suivre les prochains : [LinkedIn](https://www.linkedin.com/company/tchavi) et [X](https://x.com/tchaviai)._

