Node.js · API de maniquí invisible

API de maniquí invisible en Node.js

En una frase

Configura PHOTTA_API_KEY en env, escribe un pequeño helper con fetch para https://api.photta.app/api/v1 con un token Bearer, haz POST a /api/v1/ghost-mannequin y luego haz polling a /api/v1/ghost-mannequin/{id} cada 3 segundos hasta que el estado sea completed.

  • Tiempo típico: 1.5–4 min
  • Credits: 4 credits a 2K · 5 credits a 4K
  • Procesamiento: asíncrono, polling
  • Límites: 600 requests/min
  • Autenticación: Bearer key

Cada cifra de esta página proviene de la documentación de la API de Photta · verificado

POST/api/v1/ghost-mannequin
const res = await fetch("https://api.photta.app/api/v1/ghost-mannequin", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
    "back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
    "image_side": "front",
    "angle": "side",
    "style": "3d",
    "background_color": "white",
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "user_instruction": "closed front, cream interior lining",
    "brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
  }),
});
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/ghost-mannequin/${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");
}

De un vistazo

De un vistazo

Endpoint
POST /api/v1/ghost-mannequin
Autenticación
Bearer API key
Procesamiento
Asíncrono — realiza polling al endpoint de estado
Tiempo típico
1.5–4 min
Credits
4 credits a 2K · 5 credits a 4K
Relaciones de aspecto
3:4 · 1:1 · 4:3 · 9:16 · 16:9
Límites de entrada
Hasta 10 MB por imagen; JPEG, PNG, WebP
Idempotente
No

Cómo funciona

Cinco pasos, unos cinco minutos desde el registro hasta la primera imagen.

  1. Paso 1

    Regístrate y genera una key

    Crea una cuenta en ai.photta.app. Abre la sección Developers del dashboard, haz clic en Generate API key y copia la key en vivo. Empieza por `photta_live_` seguido de 32 caracteres hexadecimales.

  2. Paso 2

    Guarda la key en env, no en el código fuente

    Añade `PHOTTA_API_KEY=...` a `.env` (o al gestor de secretos de tu plataforma) y cárgala mediante `process.env`. Nunca hagas commit de la key; nunca la importes en un archivo marcado con `'use client'`, ya que el empaquetador la expondrá en el navegador.

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

    Escribe un pequeño helper con fetch

    Un wrapper de 20 líneas alrededor de `fetch()` gestiona la URL base, la cabecera Authorization, el cuerpo JSON y la normalización de errores. Esta es la única abstracción que necesitas hasta que se publique un SDK oficial.

    // 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. Paso 4

    Envía un try-on y haz polling

    Haz un POST a `/api/v1/ghost-mannequin` con el cuerpo de ejemplo documentado — el código siguiente incluye todos los campos. La API devuelve un ID de generación de inmediato. Haz polling a `/api/v1/ghost-mannequin/{id}` cada 3 segundos hasta que `status === 'completed'`.

    const res = await fetch("https://api.photta.app/api/v1/ghost-mannequin", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        "product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
        "back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
        "image_side": "front",
        "angle": "side",
        "style": "3d",
        "background_color": "white",
        "resolution": "2K",
        "aspect_ratio": "3:4",
        "user_instruction": "closed front, cream interior lining",
        "brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
      }),
    });
    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/ghost-mannequin/${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. Paso 5

    Persiste el resultado

    El payload completado incluye `output_url` y `thumbnail_url`. Haz fetch de los bytes una vez y almacénalos en tu propio almacenamiento de objetos — las URLs son estables, pero tu producto no debería depender de la CDN de Photta para el renderizado.

Errores

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("/ghost-mannequin", { method: "POST", body: JSON.stringify({
    "product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
    "back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
    "image_side": "front",
    "angle": "side",
    "style": "3d",
    "background_color": "white",
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "user_instruction": "closed front, cream interior lining",
    "brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
  }) });
} 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;
}

Límites

  • 600 solicitudes por minuto por API key (Ventana fija; el límite es el mismo en todos los planes)
  • Un 429 incluye Retry-After: 30 segundos
  • Hasta 10 MB por imagen
  • Formatos: JPEG, PNG, WebP
  • Lotes simultáneos por plan: Free 1 · Hobby 2 · Seller 3 · Brand 4 · Studio 6
  • Solo polling — la API no envía webhooks.

Por qué un helper supera a una biblioteca de cliente — por ahora

  • Node 18+ incluye fetch nativo — sin dependencias en tiempo de ejecución
  • El mismo helper funciona dentro de route handlers de Next.js, server actions y cualquier worker
  • Mantén la clave de API en variables de entorno; nunca importes el helper desde un módulo 'use client' o el empaquetador la filtrará
  • Funciona con cualquier cola o cron: BullMQ, Inngest, Trigger.dev, Vercel Cron, Cloudflare Queues

Lo que no hace

  • Todavía no hay un paquete oficial @photta/node — REST es la única vía actualmente
  • El polling es la única señal de finalización; la API no envía webhooks
  • Solo clave Bearer; no hay flujo OAuth client-credentials

Preguntas frecuentes de desarrolladores

¿Existe un SDK oficial de Photta para Node.js?

Todavía no. La vía documentada es REST directo mediante el fetch nativo de Node — el helper de esta página cubre toda la superficie de integración y funciona igual para cada capability.

¿Qué versiones de Node.js son compatibles?

Cualquiera compatible con `fetch` nativo — lo que implica Node 18 y versiones posteriores. En Node 16 puedes usar un polyfill con `node-fetch` o actualizar. El resto del código es ECMAScript 2020 estándar (async/await, template literals, imports dinámicos), por lo que no hay otros requisitos de entorno.

¿Cómo me autentico desde Node?

Envía `Authorization: Bearer photta_live_xxx` en cada petición. Guarda la clave en una variable de entorno y cárgala mediante `process.env`; nunca la importes desde un archivo que se ejecute en el navegador.

¿Cómo gestiono la ventana de generación?

El endpoint de generación es completamente asíncrono. El POST devuelve un ID de generación de inmediato con `status: 'processing'`. Haz polling con `GET /api/v1/ghost-mannequin/{id}` cada 3–5 segundos hasta que el `status` cambie a `completed` o `failed`, y limita los intentos para que un trabajo atascado no bloquee una petición.

¿Puedo llamar a la API dentro de Next.js?

Sí — desde un route handler (`app/**/route.ts`), una server action (`'use server'`) o middleware. Mantén la clave de API en el servidor; nunca importes el helper desde un archivo `'use client'`. Para tareas de larga duración que superen el tiempo de espera de función de Vercel, delega el polling a una cola (Inngest, Trigger.dev, BullMQ) y devuelve el ID de generación al navegador para que haga polling a tu propio status endpoint.

¿Cómo señala los errores la API?

Las respuestas que no son 2xx incluyen un cuerpo JSON con un objeto `error`: `type`, `code`, `message` y un ID de petición para soporte. Casos habituales: 400 invalid_request_error con `param` apuntando al campo incorrecto; 402 insufficient_credits con los totales de `required` y `available`; 429 rate_limit_exceeded con `retry_after` en segundos y una cabecera Retry-After coincidente. Es seguro reintentar los errores 5xx con retroceso exponencial.

Páginas relacionadas

Cada cifra de esta página proviene de la documentación de la API de Photta · verificado

Prueba la API en cinco minutos

Genera una API key de Photta, pega el snippet de cURL de arriba y tendrás tu primera imagen con modelo antes de que termine de prepararse el café.

  • 1.5–4 min
  • 4 credits a 2K · 5 credits a 4K
  • asíncrono, polling
  • 600 requests/min
  • Bearer key