Node.js · API виртуального try-on

API виртуального try-on на Node.js

В двух словах

Задайте PHOTTA_API_KEY в env, напишите небольшую вспомогательную функцию с fetch для https://api.photta.app/api/v1 с ключом Bearer, отправьте POST к /api/v1/tryon/apparel, затем выполняйте polling /api/v1/tryon/apparel/{id} каждые 3 секунды, пока статус не станет completed.

  • Типичное время: 1.5–4 min
  • Кредиты: 5 credits при 2K · 6 credits при 4K
  • Обработка: async, polling
  • Лимиты: 600 запросов/мин
  • Аутентификация: Ключ Bearer

Все показатели на этой странице взяты из документации API Photta · проверено

POST/api/v1/tryon/apparel
const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "product_type": "top",
    "product_images": [
      "https://cdn.yourstore.com/products/shirt-front.jpg"
    ],
    "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
    "product_category": "t_shirt",
    "mannequin_id": "mnk_f3a9b1",
    "pose_id": "pose_c2d4e5",
    "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
    "lock_background": true,
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "body_size": "M",
    "expression": "neutral",
    "user_instruction": "Keep the collar pattern crisp and centered."
  }),
});
const { data } = await res.json(); // data.id, data.status

async function waitFor(id) {
  for (let attempt = 0; attempt < 120; attempt++) {
    const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } });
    const { data } = await res.json();
    if (data.status === "completed") return data.output_url;
    if (data.status === "failed") throw new Error(data.error_message ?? "generation failed");
    await new Promise((r) => setTimeout(r, 3000));
  }
  throw new Error("timed out after 6 minutes");
}

Коротко о главном

Коротко о главном

Эндпоинт
POST /api/v1/tryon/apparel
Аутентификация
API-ключ Bearer
Обработка
Асинхронно — опрашивайте эндпоинт статуса
Типичное время
1.5–4 min
Кредиты
5 credits при 2K · 6 credits при 4K
Соотношения сторон
3:4 · 1:1 · 4:3 · 9:16 · 16:9
Ограничения на входные данные
До 10 МБ на изображение; JPEG, PNG, WebP
Идемпотентность
Нет

Как это работает

Пять шагов, около пяти минут от регистрации до первого изображения.

  1. Шаг 1

    Зарегистрируйтесь и сгенерируйте ключ

    Создайте аккаунт на ai.photta.app. Откройте раздел Developers в дашборде, нажмите Generate API key и скопируйте live-ключ. Он начинается с `photta_live_`, за которым следуют 32 шестнадцатеричных символа.

  2. Шаг 2

    Храните ключ в переменных окружения, а не в исходном коде

    Добавьте `PHOTTA_API_KEY=...` в `.env` (или в хранилище секретов вашей платформы) и загружайте его через `process.env`. Никогда не коммитьте ключ в репозиторий и не импортируйте его в файлы с директивой `'use client'` — сборщик включит его в бандл для браузера.

    node --version   # 18 or later ships fetch
    echo "PHOTTA_API_KEY=photta_live_xxxxx" >> .env
  3. Шаг 3

    Напишите небольшой хелпер для fetch

    Одна обертка над `fetch()` на 20 строк берет на себя базовый URL, заголовок Authorization, тело JSON и нормализацию ошибок. Это единственная абстракция, которая вам понадобится до выхода официального SDK.

    // photta.js — one helper for every endpoint
    const BASE = "https://api.photta.app/api/v1";
    export async function photta(path, init = {}) {
      const res = await fetch(BASE + path, { ...init, headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json", ...(init.headers ?? {}) } });
      const body = await res.json();
      if (!res.ok) throw Object.assign(new Error(body.error?.message ?? res.statusText), { status: res.status, retryAfter: res.headers.get("Retry-After"), body });
      return body;
    }
  4. Шаг 4

    Запустите try-on и опрашивайте статус

    Отправьте POST на `/api/v1/tryon/apparel` с телом из документации — код ниже содержит каждое поле. API сразу возвращает ID генерации. Опрашивайте `/api/v1/tryon/apparel/{id}` каждые 3 секунды, пока `status === 'completed'`.

    const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        "product_type": "top",
        "product_images": [
          "https://cdn.yourstore.com/products/shirt-front.jpg"
        ],
        "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
        "product_category": "t_shirt",
        "mannequin_id": "mnk_f3a9b1",
        "pose_id": "pose_c2d4e5",
        "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
        "lock_background": true,
        "resolution": "2K",
        "aspect_ratio": "3:4",
        "body_size": "M",
        "expression": "neutral",
        "user_instruction": "Keep the collar pattern crisp and centered."
      }),
    });
    const { data } = await res.json(); // data.id, data.status
    
    async function waitFor(id) {
      for (let attempt = 0; attempt < 120; attempt++) {
        const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } });
        const { data } = await res.json();
        if (data.status === "completed") return data.output_url;
        if (data.status === "failed") throw new Error(data.error_message ?? "generation failed");
        await new Promise((r) => setTimeout(r, 3000));
      }
      throw new Error("timed out after 6 minutes");
    }
  5. Шаг 5

    Сохраните результат

    Готовый payload содержит `output_url` и `thumbnail_url`. Скачайте байты один раз и сохраните их в своем объектном хранилище: URL-адреса стабильны, но ваш продукт не должен зависеть от CDN Photta при отображении.

Ошибки

400invalid_request_error
The request is malformed or contains invalid parameters.
401authentication_error
The API key is missing, invalid, or expired.
402insufficient_credits_error
Your account does not have enough credits.
404not_found_error
The requested resource does not exist.
409conflict_error
The request conflicts with current state — e.g. your concurrent-batch cap is reached.
429rate_limit_error
Too many requests. Slow down and retry.Retry-After: 30
500api_error
An internal server error occurred. Retry later.
try {
  await photta("/tryon/apparel", { method: "POST", body: JSON.stringify({
    "product_type": "top",
    "product_images": [
      "https://cdn.yourstore.com/products/shirt-front.jpg"
    ],
    "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
    "product_category": "t_shirt",
    "mannequin_id": "mnk_f3a9b1",
    "pose_id": "pose_c2d4e5",
    "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
    "lock_background": true,
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "body_size": "M",
    "expression": "neutral",
    "user_instruction": "Keep the collar pattern crisp and centered."
  }) });
} catch (err) {
  if (err.status === 402) console.error("insufficient_credits_error: top up before retrying");
  else if (err.status === 429) await new Promise((r) => setTimeout(r, Number(err.retryAfter ?? 30) * 1000));
  else throw err;
}

Лимиты

  • 600 запросов в минуту на один API-ключ (Фиксированное окно; лимит одинаков для всех планов)
  • Ответ 429 содержит заголовок Retry-After: 30 секунд
  • До 10 МБ на изображение
  • Форматы: JPEG, PNG, WebP
  • Параллельных пакетов на план: Free 1 · Hobby 2 · Seller 3 · Brand 4 · Studio 6
  • Только polling — API не отправляет вебхуки.

Почему хелпер лучше клиентской библиотеки — на данный момент

  • В Node 18+ есть нативный fetch — никаких зависимостей во время выполнения
  • Один и тот же хелпер работает в route handlers Next.js, server actions и любых воркерах
  • Храните API-ключ в переменных окружения; никогда не импортируйте хелпер из модуля 'use client', иначе бандлер раскроет его
  • Работает с любой очередью или cron: BullMQ, Inngest, Trigger.dev, Vercel Cron, Cloudflare Queues

Чего он не делает

  • Официального пакета @photta/node пока нет — на сегодня доступен только REST
  • Polling — единственный способ узнать о завершении; API не отправляет вебхуки
  • Только Bearer-ключ; поток OAuth client-credentials не поддерживается

Частые вопросы разработчиков

Существует ли официальный Node.js SDK для Photta?

Пока нет. Документированный способ — чистый REST через нативный fetch в Node: хелпер на этой странице закрывает всю интеграцию и работает одинаково для каждой capability.

Какие версии Node.js поддерживаются?

Любые с поддержкой нативного `fetch` — то есть Node 18 и выше. В Node 16 нужно либо добавить полифил `node-fetch`, либо обновиться. Остальной код написан на чистом ECMAScript 2020 (async/await, шаблонные строки, динамические импорты), поэтому других системных требований нет.

Как настроить аутентификацию в Node?

Передавайте `Authorization: Bearer photta_live_xxx` в каждом запросе. Поместите ключ в переменную окружения и считывайте его через `process.env`; никогда не импортируйте его из файла, который выполняется в браузере.

Как обрабатывать время генерации?

Эндпоинт генерации полностью асинхронный. POST сразу возвращает ID генерации со значением `status: 'processing'`. Опрашивайте `GET /api/v1/tryon/apparel/{id}` каждые 3–5 секунд, пока `status` не сменится на `completed` или `failed`, и ограничьте число попыток, чтобы зависшая задача не блокировала запрос.

Можно ли вызывать API внутри Next.js?

Да — из route handler (`app/**/route.ts`), server action (`'use server'`) или middleware. Храните API-ключ на сервере; никогда не импортируйте хелпер из файла с `'use client'`. Для длительных задач, превышающих таймаут функций Vercel, перенесите polling в очередь (Inngest, Trigger.dev, BullMQ) и верните ID генерации браузеру, чтобы он опрашивал ваш собственный эндпоинт статуса.

Как API сообщает об ошибках?

Ответы с кодом, отличным от 2xx, возвращают тело JSON с объектом `error`: `type`, `code`, `message` и ID запроса для поддержки. Типичные случаи: 400 invalid_request_error с полем `param`, указывающим на некорректное поле; 402 insufficient_credits с количеством `required` и `available`; 429 rate_limit_exceeded с `retry_after` в секундах и соответствующим заголовком Retry-After. Запросы с ошибками 5xx можно безопасно повторять с экспоненциальной задержкой.

Связанные страницы

Все показатели на этой странице взяты из документации API Photta · проверено

Протестируйте API за пять минут

Сгенерируйте API-ключ Photta, вставьте пример cURL выше, и вы получите первое изображение на модели быстрее, чем сварится кофе.

  • 1.5–4 min
  • 5 credits при 2K · 6 credits при 4K
  • async, polling
  • 600 запросов/мин
  • Ключ Bearer