Comment utiliser l'API Z-Image-Turbo sur WaveSpeed (Guide étape par étape)
Une petite chose m’a menée ici. Je m’appelle Dora. Ce jour-là, j’avais besoin d’une photo de produit nette pour une variante de landing page, et mes outils d’image habituels semblaient plus lourds que la tâche. J’entendais continuellement parler de l’API Z-Image-Turbo . Pas le genre de mentions bruyantes, plutôt la manière discrète dont elle apparaissait dans les changelogs et les notes d’ingénierie. Donc j’ai essayé.
Je l’ai utilisée pendant une semaine en fin janvier 2026, surtout pour générer des visuels marketing simples et quelques textures conceptuelles pour un prototype. Rien de sophistiqué. Je voulais voir où elle s’inscrivait sans refondre ma stack. Voici ce qui a aidé, ce qui n’a pas fonctionné, et comment j’ai l’intégrée sans créer de désordre.
Préalables
Créer un compte WaveSpeed
J’ai commencé par créer un compte WaveSpeed. Le flux était basique : email, mot de passe, vérification. Aucune surprise. J’aime que le tableau de bord n’ait pas essayé de me vendre quelque chose avant que je puisse tester.
Obtenir votre clé API
Après l’inscription, je suis allée à la section développeur et j’ai généré une clé. Je l’ai étiquetée pour mon projet de test et je l’ai stockée dans mon fichier .env local. Une petite note : j’ai créé une deuxième clé pour la staging plus tard. Garder les clés délimitées par environnement m’a sauvée de me demander quels appels visaient mon forfait payant.
Concepts de base de l’API
Structure de l’URL du point de terminaison
J’ai utilisé un seul point de terminaison de génération pour la plupart des cas. Le motif ressemblait à ceci :
- Base : api.wavespeed.ai
- Chemin versionné : /v1
- Chemin produit : /z-image-turbo
- Action : /generate
Donc une URL typique ressemblait à /v1/z-image-turbo/generate. J’évite de coder en dur les versions car elles changent. J’ai mis la base et la version dans la configuration pour pouvoir les mettre à jour plus tard sans réécrire les appels.
Configuration de l’en-tête d’authentification
L’authentification était un token Bearer standard. Ce qui a aidé, c’était de garder la configuration de l’en-tête centralisée :
- Authorization: Bearer YOUR_API_KEY
- Content-Type: application/json
J’ai d’abord testé avec un timeout court (10s), puis je l’ai augmenté. Quand le service est sous charge, la génération d’images peut prendre plus de temps qu’un appel REST typique. Mieux vaut le prévoir que d’essayer à nouveau agressivement et d’atteindre les limites de débit.
Paramètres fondamentaux expliqués
prompt, Rédiger des invites efficaces
Je ne suis pas une poète du prompt. Ce qui fonctionnait ici était un langage simple et littéral avec un indice de style clair. Pour les photos de produit, j’ai trouvé une structure stable :
- Sujet : « un écouteur sans fil noir mat sur un fond blanc sans couture »
- Angle ou objectif : « angle de 45 degrés, éclairage studio doux »
- Indices de contexte : « ombre minimale, pas de reflets »
J’ai évité d’empiler les styles (« cinématique, sombre, éditorial, brillant ») car les images dérivaient. Les prompts courts et simples donnaient des résultats plus prévisibles.
Deux passes m’ont aidée à itérer :
- Première passe : prompt large pour voir la composition.
- Deuxième passe : ajouter les contraintes uniquement où nécessaire (longueur de l’ombre, texture de fond).
size, Résolutions supportées (256-1536)
J’ai utilisé surtout des tailles carrées et en portrait. L’API acceptait des valeurs courantes de 256 jusqu’à 1536. Je m’en tenais à 512 pour les aperçus rapides et 1024 pour les actifs finaux. 1536 produisait de jolis détails mais cela coûtait plus de temps et de tokens. Si vous générez de nombreuses variantes, commencez petit et montez en résolution ou réexécutez les prompts sélectionnés à une taille plus grande.
seed, Contrôle de la reproductibilité
Le seed a compté plus que je ne l’aurais prévu. Quand j’ai trouvé un look que j’aimais, j’ai sauvegardé le seed pour pouvoir ajuster seulement une variable (comme la densité de fond) sans dériver trop loin. Si j’ai beaucoup changé le prompt, j’ai effacé le seed pour éviter de combattre le biais antérieur du modèle. En pratique : j’ai enregistré le prompt, la taille et le seed par ID d’image. Cela a rendu les réexécutions ennuyeuses, d’une bonne manière.
output_format, JPEG vs PNG vs WebP
- JPEG : léger et rapide. Bon pour les aperçus et les actifs web sans transparence.
- PNG : sans perte, plus volumineux. Je l’utilisais pour les superpositions UI ou quand j’avais besoin d’un bord net.
- WebP : un bon juste milieu. Plus petit que PNG, plus net qu’un JPEG agressif.
Quand je faisais des lots, j’ai défini les aperçus sur JPEG et les finales sur PNG ou WebP selon la cible. Mélanger les formats dans une exécution est possible, mais j’ai gardé la cohérence par travail pour réduire la comptabilité.
Exemples de code
Démarrage rapide cURLVoici le chemin le plus court que j’ai utilisé pour vérifier le point de terminaison. J’aime commencer avec cURL car cela rend les messages d’erreur évidents.
Bash
curl -X POST https://api.wavespeed.ai/v1/z-image-turbo/generate \
-H "Authorization: Bearer $WAVESPEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a matte black wireless earbud on a white seamless background, soft studio lighting",
"size": 1024,
"seed": 12345,
"output_format": "webp",
"enable_sync_mode": true
}' \
--output preview.webp
Si le mode sync est activé et supporté pour votre requête, la réponse retourne les données d’image binaires ou un champ base64 (dépend des en-têtes et des paramètres du serveur). Je l’ai sauvegardé directement dans un fichier pendant les tests.
Implémentation PythonJ’ai gardé la version Python simple. Requests, un timeout, et des vérifications d’erreur basiques.
Python
import os
import requests
import base64
API_KEY = os.getenv("WAVESPEED_API_KEY")
URL = "https://api.wavespeed.ai/v1/z-image-turbo/generate"
payload = {
"prompt": "cozy reading nook by a window, soft morning light, minimalist",
"size": 1024,
"output_format": "png",
"enable_sync_mode": True,
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
resp = requests.post(URL, json=payload, headers=headers, timeout=30)
resp.raise_for_status()
# Some deployments return JSON with base64. Others return binary directly.
content_type = resp.headers.get("Content-Type", "")
if "application/json" in content_type:
data = resp.json()
img_b64 = data.get("image_base64")
if not img_b64:
raise RuntimeError("No image in response")
with open("output.png", "wb") as f:
f.write(base64.b64decode(img_b64))
else:
with open("output.png", "wb") as f:
f.write(resp.content)
print("Saved output.png")
Exemple JavaScript/Node.jsEn Node, je préfère fetch avec AbortController pour les timeouts.
JavaScript
import fetch from 'node-fetch';
import { writeFileSync } from 'fs';
const API_KEY = process.env.WAVESPEED_API_KEY;
const URL = "https://api.wavespeed.ai/v1/z-image-turbo/generate";
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const payload = {
prompt: "abstract paper texture, soft fibers, off-white, subtle grain",
size: 512,
output_format: "jpeg",
enable_sync_mode: true,
};
try {
const resp = await fetch(URL, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: controller.signal,
});
if (!resp.ok) {
throw new Error(`${resp.status} ${resp.statusText}`);
}
const buffer = Buffer.from(await resp.arrayBuffer());
writeFileSync("out.jpg", buffer);
console.log("Saved out.jpg");
} catch (err) {
console.error("Request failed:", err.message);
} finally {
clearTimeout(timeoutId);
}
Mode asynchrone vs mode synchrone
enable_sync_mode expliqué
Quand enable_sync_mode était true, les petites images (512–1024) arrivaient souvent en une seule passe. C’était agréable pour les aperçus et les cas uniques. Mais le mode sync expire sous charge lourde ou avec des tailles plus grandes. Quand j’ai atteint cette limite, l’API retournait un job ID à la place d’une image.
Sondage des résultats
L’async a pris une seconde à câbler, puis c’était bon. Le flux :
- POST une requête de génération avec enable_sync_mode défini à false (ou simplement l’omettre).
- Recevoir un job_id dans le JSON.
- Sonder un point de terminaison de statut toutes les 1–2 secondes avec backoff exponentiel.
- Quand le statut est terminé, télécharger l’image depuis l’URL fournie.
J’ai plafonné le sondage à ~30 secondes avant d’échouer gracieusement. En production, je changerais en webhook si le service l’offre. Le sondage fonctionne, mais ce n’est pas romantique.
Gestion des erreurs
Codes d’erreur courants
Ce que j’ai vu pendant les tests :
- 400 : un mauvais champ (taille hors limites, format inconnu). Corrigez votre payload.
- 401 : token Bearer manquant ou invalide. Vérifiez l’en-tête et le scope de la clé.
- 404 : chemin de point de terminaison incorrect ou job non trouvé (généralement une faute de frappe).
- 429 : limite de débit atteinte. Attendez et réessayez plus tard.
- 500/503 : problème de service transitoire. Réessayez avec jitter.
J’ai enregistré les corps de réponse car ils contenaient souvent un indice en langage courant, utile quand la taille ou le seed se voyaient rejeter.
Bonnes pratiques de limite de débit
J’ai utilisé un token bucket sur le client, avec une petite queue. Si l’API retournait 429, j’attendais exponentiellement et j’ajoutais du jitter aléatoire (50–200ms) pour éviter les troupeaux tonnerre. De plus, le traitement par lot des requêtes (voir ci-dessous) réduisait les pics.
Conseils pour la production
Motif de traitement par lot
Générer une image par requête semblait simple, mais c’était du gaspillage de surcharge. Je suis passée à une petite taille de lot (3–5 prompts chacun) et j’ai obtenu un débit plus stable. J’ai écrit les résultats dans un dossier daté avec un fichier manifest.json qui sauvegardait prompt, taille, seed et chemin de sortie. Ce manifeste s’est avéré pratique quand un stakeholder a dit : « Pouvons-nous faire la même chose, mais avec une lumière plus chaude ? » J’ai juste augmenté le seed ou ajusté le prompt et réexécuté.
Optimisation des coûts
Trois choses importaient :
- Discipline de taille : aperçu à 512, finaliser à 1024 seulement si nécessaire. 1536 uniquement pour l’utilisation à heavy crop.
- Ajustement du format : JPEG pour les aperçus : WebP pour la plupart des builds web : PNG quand la transparence est non-négociable.
- Arrêt précoce : si les premières images semblaient mauvaises, j’annulais le travail au lieu d’attendre. Cela économisait des centimes, qui s’accumulent.
Une note pratique supplémentaire : j’ai plafonné la dépense quotidienne totale dans mes scripts. Pas glamour, mais c’est ce qui m’a protégée d’une boucle incontrôlée.
Quelques observations de clôture. L’API Z-Image-Turbo ne semblait pas magique. C’est probablement pour ça que j’aimais. C’était stable une fois que j’ai mis en place les garde-fous : seeds fixes pour la reproductibilité, petits lots, timeouts conservateurs. Le mode sync était agréable pour les aperçus : l’async avait du sens pour tout le reste. Si vous nagez déjà dans les outils IA, celui-ci ne criera pas pour attirer l’attention. Il s’assoira tranquillement dans le pipeline et fera son travail.
Je suis toujours curieuse de savoir comment il gère les prompts bruyants à résolutions plus élevées. C’est un test pour un autre jour.





