El motor de home staging virtual que hay detrás de la aplicación web ya está disponible como API HTTP. Envías la foto de una habitación, indicas un tipo de estancia y un estilo de mobiliario, y la imagen amueblada vuelve en la misma respuesta. Cuesta $0.15 por imagen, no hay suscripción, ni mínimo, ni factura mensual, y la documentación de referencia está en stagify.ai/developers.html.

Esta entrada es lo que una página de referencia no puede ser: por qué la API tiene la forma que tiene, y el puñado de cosas que te harán tropezar la primera tarde.

Toda la API es un único endpoint

Hay exactamente una llamada que cuesta dinero.

curl https://stagify.ai/api/v1/renders \
  -H "Authorization: Bearer $STAGIFY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "image=@living-room.jpg" \
  -F "roomType=Living room" \
  -F "furnitureStyle=modern" \
  -F "labelVirtuallyStaged=true"

Esa es la integración. La respuesta lleva la imagen amueblada como data URL, junto con un estado y un id de petición. Todo lo demás en la API es gratuito y de solo lectura: GET /api/v1/options te da los valores admitidos de cada enum, GET /api/v1/me te dice si tu clave funciona, GET /api/v1/credits informa del saldo y GET /api/v1/renders/{id} recupera un render concreto.

Las peticiones son multipart/form-data, porque estás subiendo una fotografía y base64 en un cuerpo JSON te costaría un tercio más de bytes a cambio de nada. Las imágenes son JPEG, PNG o WebP de hasta 25 MB, y lo que se comprueba es el tipo MIME, no la extensión del archivo.

Por qué no hay cola de trabajos

El diseño obvio para una API de imágenes lenta es aceptar la petición, devolver un id de trabajo y hacer que el cliente consulte el resultado. Deliberadamente no lo hicimos, y vale la pena explicar por qué, porque desde fuera parece un atajo y no lo es.

Esta aplicación no tiene ninguna cola de trabajos. Añadir una solo para la API significaría introducir una cola cuyos trabajos viven en un único proceso, en un host que se reinicia con cada despliegue. Cuando eso ocurre a mitad de un render, el crédito del cliente ya está gastado y no queda ningún socket para avisarle. Ese fallo es estrictamente peor que una conexión caída, porque una conexión caída puede reintentarse con la misma clave de idempotencia y no cuesta nada.

Así que un render mantiene la conexión abierta hasta que termina, lo que lleva desde unos segundos hasta un par de minutos. Configura el read timeout de tu cliente en 300 segundos. El valor por defecto en la mayoría de bibliotecas HTTP es 30 o 60, y un cliente que se rinde a los 30 segundos parecerá una API que falla constantemente mientras gasta créditos en silencio en renders que nunca recoge.

Construida para poder cambiar más adelante sin romperte nada: el cuerpo de éxito ya lleva status: "succeeded", y GET /api/v1/renders/{id} ya existe. Si algún día añadimos una cola, llegará como un nuevo valor de status en el mismo endpoint, no como una API nueva. Trata cualquier estado distinto de succeeded como «ve a consultar el GET» y tu cliente ya estará preparado para el futuro.

El mismo razonamiento limita variations a 1. Una petición es un crédito es una imagen es un reembolso, sin aritmética de reparto parcial en ninguna parte del dinero. Si quieres tres versiones de una habitación, lanza tres peticiones simultáneas: consigues así mejor paralelismo del que nunca te dio el fan-out.

Créditos prepago, y por qué eso es una medida de seguridad

Compras créditos por adelantado. Cada render correcto gasta uno, y un render que falla se reembolsa automáticamente, así que nunca pagas por una foto que no recibiste. Los packs empiezan en 20 créditos por $3, y el precio por imagen baja cuanto más grandes son, hasta $0.12 por imagen en el pack de 500 créditos. La tabla en vivo está en la página de documentación de la API, generada a partir de los mismos datos con los que cobra el webhook de pago, de modo que no puede desviarse de lo que realmente se te factura.

La facturación por consumo habría sido más fácil de construir. El prepago es mejor para ti en un aspecto concreto: una clave robada te puede costar como mucho el saldo que le quede. No hay factura a final de mes, ni excesos, ni ningún escenario en el que una clave filtrada se convierta en una factura de cuatro cifras antes de que nadie se dé cuenta. El corte en seco al llegar a cero es el objetivo, no una limitación, y por eso también cada respuesta lleva una cabecera X-Stagify-Credits-Remaining: puedes ver acercarse el muro sin tener que consultarlo.

Las claves están pensadas solo para uso de servidor a servidor. La API no envía cabeceras CORS, así que el JavaScript del navegador no puede llamarla, y eso es deliberado, no una omisión que pensemos corregir. Una clave accesible desde un navegador es una clave filtrada. Mostramos una clave nueva exactamente una vez, guardamos solo un hash de ella y la revocamos al instante desde el panel de claves de API.

Cuatro cosas que te harán tropezar

Toda API tiene comportamientos perfectamente razonables una vez explicados y desconcertantes antes de explicarlos. Aquí están los nuestros, dichos con claridad en lugar de dejarlos en una tabla de parámetros.

  1. roomType distingue mayúsculas y espacios, y un valor erróneo no se rechaza. Se escribe en el prompt como texto libre, así que Living Room no es Living room. Obtendrás un render, solo que no el que querías, y lo habrás pagado. El conjunto admitido es Bedroom, Living room, Dining room, Kitchen, Office, Bathroom, Outdoors y Dorm. Sácalo de GET /api/v1/options en lugar de reescribirlo en tu propio desplegable.
  2. Un furnitureStyle no reconocido se convierte silenciosamente en standard. El mismo tipo de problema, con un síntoma más callado: una errata en el nombre de un estilo no da error, simplemente te deja el aspecto por defecto en todas las imágenes del lote.
  3. removeFurniture acepta true u on, y nada más. Ni 1, ni yes. Los dos se leen como false, y obtienes una foto amueblada con el sofá viejo todavía dentro. Su compañero keepFurniture admite texto libre indicando qué dejar en su sitio, hasta 500 caracteres, y solo se lee cuando removeFurniture está realmente activado.
  4. Envía una Idempotency-Key en cada petición. Omitirla está permitido y significa que no hay protección frente a repeticiones, que es el comportamiento honesto por defecto; no vamos a derivarla de tu foto, porque entonces dos renders genuinamente distintos de la misma habitación colisionarían y al segundo llamante se le entregaría la imagen del primero. Reintenta con el mismo valor y una petición completada se reproduce desde el almacenamiento con X-Stagify-Replayed: true y sin un segundo cargo. Reutiliza una con parámetros diferentes y obtienes 422 IDEMPOTENCY_KEY_REUSED, que es la API diciéndote que tu generador de claves tiene un bug.

Más allá de eso, los parámetros que conviene conocer reflejan los de la aplicación web: additionalPrompt para indicaciones en texto libre, y hasta cinco fotos de referencia furnitureImage con las que el modelo pueda compararse.

La etiqueta de divulgación no se puede desactivar

La mayoría de las normas de la MLS, y la guía de la NAR que hay detrás de ellas, exigen que una foto de anuncio amueblada digitalmente indique que lo está; desde enero de 2026, la AB 723 de California lo exige por ley. Hemos escrito la versión estado por estado de eso con detalle.

labelVirtuallyStaged graba la divulgación en los píxeles en lugar de adjuntarla como metadatos, porque los metadatos los elimina prácticamente cualquier herramienta por la que pase una imagen camino de un portal. Tú controlas su aspecto: stampStyle elige entre dark, light, minimal y banner, stampScale lo dimensiona entre 0.7 y 1.6, y stampLang lo representa en cualquiera de once idiomas.

Lo que no controlas es el modo de fallo. Si se pide la etiqueta y no se puede aplicar, la imagen se retiene en lugar de entregarse sin etiquetar. La llamada responde 500 DISCLOSURE_STAMP_FAILED y tu crédito se reembolsa. No hay ninguna opción para desactivar eso, ni un valor por defecto por clave que lo apague sin hacer ruido. La alternativa es enviar una foto amueblada sin etiquetar a un cliente que pidió explícitamente que se etiquetara, que es el único bug de este sistema capaz de poner a alguien delante de un regulador.

Errores, y la única regla sobre ellos

Todo fallo responde con un cuerpo JSON que lleva tanto una cadena error como un code. Ramifica según el código, nunca según el mensaje. Los mensajes se reescriben; los códigos no.

Los que te encontrarás de verdad en producción son 402 INSUFFICIENT_CREDITS (recarga; el cuerpo lleva el saldo restante), 409 CONCURRENCY_LIMIT (demasiados renders en curso con una misma clave; el margen por defecto es de tres a la vez, así que espera un segundo y reintenta), 409 REQUEST_IN_FLIGHT (has reintentado una clave de idempotencia que sigue en ejecución, así que espera en lugar de insistir), 429 RATE_LIMITED y 422 NO_IMAGE_GENERATED, que significa que el modelo no devolvió nada y se reembolsa automáticamente. El límite de renders es actualmente de 60 por clave cada cinco minutos; si necesitas más, o una factura en lugar de una tarjeta, escríbenos.

Fíjate en el patrón común a todos ellos: todo lo que falla después de un cargo se reembolsa. Ese invariante es la razón de que la API sea síncrona y de que variations esté limitado a uno, y es lo que mantendríamos aunque hubiera que renunciar a funcionalidades.

Para quién es esto, y para quién no

La API existe para los casos en los que el staging es un paso dentro del pipeline de otro y no algo que una persona se sienta a hacer:

No es en absoluto para una persona que amuebla una sola casa. Si ese eres tú, la aplicación web hace más que la API, ya que Masking Studio y el AI Designer no tienen equivalente en la API, y es gratis y sin marca de agua. Pagar $0.15 por hacer en código lo que puedes hacer gratis en un navegador sería una elección extraña, y preferimos decírtelo a venderte un pack de créditos.

Primeros pasos

Crea una clave en la página de claves de API, compra el pack más pequeño y gasta un crédito en una foto cuyo resultado ya conozcas. Luego lee bien la referencia antes de escribir el bucle por lotes, sobre todo la tabla de idempotencia. Veinte créditos son $3, y con eso sobra para averiguar si esto encaja en tu pipeline.

Fuentes y notas