Как использовать Z-Image-Turbo API на WaveSpeed (пошаговое руководство)

Как использовать Z-Image-Turbo API на WaveSpeed (пошаговое руководство)

Маленький поводок привел меня сюда. Я Дора. В тот день мне нужна была чистая предметная фотография для варианта посадочной страницы, а мои обычные инструменты для изображений казались тяжелее задачи. Я постоянно слышала о Z-Image-Turbo​ ​​​API​. Не громких упоминаний, скорее это тихо появлялось в журналах изменений и инженерных заметках. Так что я попробовала.

Я использовала его в течение недели в конце января 2026 года, в основном для создания простых маркетинговых визуалов и нескольких концептуальных текстур для прототипа. Ничего сложного. Я хотела посмотреть, где он вписывается, без переделки моего стека. Вот что помогло, что нет, и как я все это подключила без беспорядка.

Предварительные требования

Создание учетной записи WaveSpeed

Я начала с создания учетной записи WaveSpeed. Процесс был базовым: адрес электронной почты, пароль, подтверждение. Никаких сюрпризов. Мне нравится, что панель управления не пыталась что-то мне продать до того, как я смогла протестировать.

Получение вашего API ключа

После регистрации я перешла в раздел разработчика и сгенерировала ключ. Я пометила его для моего тестового проекта и сохранила в локальном файле .env. Одна маленькая заметка: я создала второй ключ для staging позже. Привязка ключей к окружению спасла меня от гадания, какие вызовы попадают в мой оплачиваемый тариф.

Основы API

Структура URL конечной точки

Я использовала один конечную точку generate для большинства случаев. Шаблон выглядел так:

  • Базовый адрес: api.wavespeed.ai
  • Версионный путь: /v1
  • Путь продукта: /z-image-turbo
  • Действие: /generate

Таким образом, типичный URL выглядел как /v1/z-image-turbo/generate. Я избегаю жесткого кодирования версий, потому что они меняются. Я поместила базовый адрес и версию в конфиг, чтобы позже я могла обновить это без переписывания вызовов.

Настройка заголовка аутентификации

Аутентификация была стандартным Bearer токеном. Что помогло, это сохранение настройки заголовка в центре:

  • Authorization: Bearer YOUR_API_KEY
  • Content-Type: application/json

Я тестировала с маленьким timeout (10s) в первый раз, потом увеличила его. Когда сервис под нагрузкой, создание изображения может занять больше времени, чем типичный вызов REST. Лучше планировать это заранее, чем настойчиво повторять попытки и попасть на лимит.

Объяснение основных параметров

prompt, Написание эффективных запросов

Я не поэтесса запросов. Что здесь сработало, это простой, буквальный язык и один четкий стиль. Для предметных фотографий я нашла стабильную структуру:

  • Объект: “черный матовый беспроводной наушник на белом однородном фоне”
  • Угол или объектив: “угол 45 градусов, мягкое студийное освещение”
  • Контекстные подсказки: “минимальная тень, без отражений”

Я избегала наслаивания стилей (“кинематографичный, мрачный, редакторский, глянцевый”), потому что изображения дрейфовали. Короткие, простые запросы давали более предсказуемые результаты.

Два прохода помогли мне итерировать:

  • Первый проход: широкий запрос, чтобы увидеть композицию.
  • Второй проход: добавление ограничений только там, где необходимо (длина тени, текстура фона).

size, Поддерживаемые разрешения (256-1536)

Я использовала квадратные и портретные размеры в основном. API принимал распространенные значения от 256 до 1536. Я придерживалась 512 для быстрых превью и 1024 для финальных активов. 1536 давал хороший уровень детализации, но стоит больше времени и токенов. Если вы создаете много вариантов, начните с малого и увеличьте масштаб или повторно запустите выбранные запросы с большим размером.

seed, Управление воспроизводимостью

Seed имел большее значение, чем я ожидала. Когда я нашла понравившийся мне вид, я сохранила seed, чтобы могла изменить только одну переменную (например, плотность фона) без значительного дрейфа. Если я значительно изменила запрос, я очистила seed, чтобы избежать сопротивления более ранней предвзятости модели. На практике: я логировала prompt, size и seed для каждого ID изображения. Это сделало переформатирование скучным, в хорошем смысле.

output_format, JPEG vs PNG vs WebP

  • JPEG: легкий и быстрый. Хорошо для превью и веб-активов без прозрачности.
  • PNG: ​без потерь, больший размер. Я использовала его для наложений UI или когда нужна была чистая граница.
  • WebP: ​хороший компромисс. Меньше чем PNG, чище чем агрессивный JPEG.

Когда я работала пакетно, я устанавливала превью на JPEG и финальные на PNG или WebP в зависимости от цели. Смешивание форматов в одном запуске возможно, но я держала это последовательным для каждого задания, чтобы снизить ведение записей.

Примеры кода

cURL быстрый стартВот самый короткий путь, который я использовала для проверки конечной точки. Мне нравится начинать с cURL, потому что это делает сообщения об ошибках очевидными.

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

Если синхронный режим включен и поддерживается для вашего запроса, ответ возвращает бинарные данные изображения или поле base64 (зависит от заголовков и настроек сервера). Я сохранила его прямо в файл во время тестов.

Реализация на PythonЯ держала версию Python простой. Requests, timeout и базовые проверки ошибок.

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")

Пример JavaScript/Node.jsВ Node я предпочитаю fetch с AbortController для timeout.

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);
}

Асинхронный vs синхронный режим

enable_sync_mode объяснено

Когда enable_sync_mode был истинным, маленькие изображения (512–1024) часто приходили сразу. Это было приятно для превью и одноразовых случаев. Но синхронный режим истекает при высокой нагрузке или больших размерах. Когда я столкнулась с этим, API возвращал ID задания вместо изображения.

Опрос результатов

Асинхронный режим занял секунду на подключение, потом все было хорошо. Процесс:

  • POST запрос generate с enable_sync_mode установленным в false (или просто опустить его).
  • Получить job_id в JSON.
  • Опрашивать конечную точку статуса каждые 1–2 секунды с экспоненциальным отступом.
  • Когда статус done, загрузить изображение из предоставленного URL.

Я ограничила опрос примерно 30 секундами перед корректным отказом. В production я бы переключилась на webhook, если сервис его предлагает. Опрос работает, но это не романтично.

Обработка ошибок

Распространенные коды ошибок

Что я видела во время тестов:

  • 400: неверное поле (размер вне диапазона, неизвестный формат). Исправьте ваш payload.
  • 401: отсутствует или недействительный Bearer токен. Проверьте заголовок и область действия ключа.
  • 404: неверный путь конечной точки или задание не найдено (обычно опечатка).
  • 429: лимит. Отступите и повторите попытку позже.
  • 500/503: временная проблема с сервисом. Повторите попытку с jitter.

Я логировала тела ответов, потому что они часто содержали обычную подсказку, полезную когда размер или seed были отклонены.

Лучшие практики по ограничению частоты

Я использовала token bucket на клиенте с короткой очередью. Если API вернул 429, я отступала экспоненциально и добавила случайный jitter (50–200ms), чтобы избежать громкой толпы. Также пакетная обработка запросов (см. ниже) снизила пиковые всплески.

Советы для production

Паттерн пакетной обработки

Создание одного изображения за запрос казалось простым, но оно растрачивало накладные расходы. Я перешла на небольшой размер пакета (3–5 запросов каждый) и получила более стабильную пропускную способность. Я написала результаты в датированную папку с файлом manifest.json, который сохранял prompt, size, seed и путь вывода. Этот манифест оказался полезным, когда заинтересованное лицо сказало: “Можем ли мы сделать то же самое, но с более теплым светом?” Я просто увеличила seed или подправила запрос и повторно запустила.

Оптимизация затрат

Три вещи имели значение:

  • Дисциплина размера: превью в 512, финализирование в 1024 только когда необходимо. 1536 только для использования с обрезкой.
  • Соответствие формата: JPEG для превью: WebP для большинства веб-сборок: PNG когда прозрачность критична.
  • Раннее прекращение: если первые кадры выглядели неправильно, я отменяла задание вместо ожидания. Это сэкономило копейки, которые накапливаются.

Еще одна практическая заметка: я ограничила общие дневные расходы в моих скриптах. Не гламурно, но это защитило меня от цикла с потерями.

Несколько завершающих наблюдений. Z-Image-Turbo API не казался волшебным. Вероятно, поэтому мне он понравился. Он был стабильным, когда я установила guardrails: фиксированные seeds для воспроизводимости, маленькие пакеты, консервативные timeout. Синхронный режим был приятен для превью: асинхронный имел смысл для всего остального. Если вы уже плаваете в AI инструментах, этот не будет громко требовать внимания. Он тихо сидит в pipeline и делает свою работу.

Я все еще любопытна, как он справляется с шумными запросами при больших разрешениях. Это тест на другой день.