O motor de home staging virtual por trás do aplicativo web agora está disponível como API HTTP. Você envia a foto de um ambiente, informa um tipo de cômodo e um estilo de mobília, e a imagem mobiliada volta na mesma resposta. Custa $0.15 por imagem, não há assinatura, nem mínimo, nem fatura mensal, e a documentação de referência está em stagify.ai/developers.html.

Este texto é o que uma página de referência não consegue ser: por que a API tem o formato que tem, e o punhado de coisas que vão te derrubar na primeira tarde.

A API inteira é um único endpoint

Existe exatamente uma chamada que custa dinheiro.

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"

Essa é a integração. A resposta traz a imagem mobiliada como data URL, junto com um status e um id de requisição. Todo o resto da API é gratuito e somente leitura: GET /api/v1/options entrega os valores aceitos de cada enum, GET /api/v1/me informa se sua chave funciona, GET /api/v1/credits reporta o saldo e GET /api/v1/renders/{id} recupera um render específico.

As requisições são multipart/form-data, porque você está enviando uma fotografia e base64 num corpo JSON custaria um terço a mais de bytes por nada. As imagens são JPEG, PNG ou WebP de até 25 MB, e o que é verificado é o tipo MIME, não a extensão do arquivo.

Por que não existe fila de trabalhos

O desenho óbvio para uma API de imagens lenta é aceitar a requisição, devolver um id de job e fazer o cliente consultar o resultado. Deliberadamente não fizemos isso, e vale dizer o motivo, porque de fora parece um atalho e não é.

Este aplicativo não tem fila de trabalhos alguma. Adicionar uma só para a API significaria introduzir uma fila cujos jobs vivem em um único processo, num host que reinicia a cada deploy. Quando isso acontece no meio de um render, o crédito do cliente já foi gasto e não sobra socket nenhum para avisá-lo. Essa falha é estritamente pior que uma conexão caída, porque uma conexão caída pode ser repetida com a mesma chave de idempotência e não custa nada.

Então um render segura a conexão até terminar, o que leva de alguns segundos a alguns minutos. Configure o read timeout do seu cliente em 300 segundos. O padrão na maioria das bibliotecas HTTP é 30 ou 60, e um cliente que desiste aos 30 segundos vai parecer uma API que falha o tempo todo enquanto gasta créditos em silêncio em renders que nunca recolhe.

Feita para mudar depois sem quebrar você: o corpo de sucesso já carrega status: "succeeded", e GET /api/v1/renders/{id} já existe. Se algum dia acrescentarmos uma fila, ela chegará como um novo valor de status no mesmo endpoint, não como uma API nova. Trate qualquer status diferente de succeeded como “vá consultar o GET” e seu cliente já está preparado para o futuro.

O mesmo raciocínio limita variations a 1. Uma requisição é um crédito é uma imagem é um estorno, sem aritmética de distribuição parcial em nenhum ponto do dinheiro. Se você quer três versões de um ambiente, dispare três requisições simultâneas: assim você obtém mais paralelismo do que o fan-out jamais deu.

Créditos pré-pagos, e por que isso é um recurso de segurança

Você compra créditos antecipadamente. Cada render bem-sucedido gasta um, e um render que falha é estornado automaticamente, então você nunca paga por uma foto que não recebeu. Os pacotes começam em 20 créditos por $3, e o preço por imagem cai conforme eles crescem, até $0.12 por imagem no pacote de 500 créditos. A tabela atualizada está na página de documentação da API, gerada a partir dos mesmos dados contra os quais o webhook de pagamento cobra, de modo que ela não pode divergir do que você é de fato cobrado.

Cobrança por uso teria sido mais fácil de construir. O pré-pago é melhor para você num ponto específico: uma chave roubada pode te custar no máximo o saldo que restar nela. Não há fatura no fim do mês, não há excedente, e não existe cenário em que uma chave vazada vire uma conta de quatro dígitos antes de alguém perceber. A parada seca no zero é o objetivo, não uma limitação, e é também por isso que toda resposta carrega um cabeçalho X-Stagify-Credits-Remaining: você vê o muro se aproximar sem precisar consultar.

As chaves são destinadas apenas a uso servidor a servidor. A API não envia cabeçalhos CORS, então o JavaScript do navegador não consegue chamá-la, e isso é proposital, não uma omissão que pretendemos corrigir. Uma chave alcançável a partir de um navegador é uma chave vazada. Mostramos uma chave nova exatamente uma vez, guardamos apenas um hash dela e revogamos na hora pelo painel de chaves da API.

Quatro coisas que vão te derrubar

Toda API tem comportamentos perfeitamente razoáveis depois de explicados e desconcertantes antes disso. Aqui estão os nossos, ditos com clareza em vez de deixados numa tabela de parâmetros.

  1. roomType diferencia maiúsculas e espaços, e um valor errado não é rejeitado. Ele é escrito no prompt como texto livre, então Living Room não é Living room. Você vai receber um render, só que não o que queria, e terá pago por ele. O conjunto aceito é Bedroom, Living room, Dining room, Kitchen, Office, Bathroom, Outdoors e Dorm. Puxe-o de GET /api/v1/options em vez de redigitá-lo no seu próprio dropdown.
  2. Um furnitureStyle não reconhecido vira silenciosamente standard. Mesma classe de problema, sintoma mais discreto: um erro de digitação no nome de um estilo não dá erro, só entrega o visual padrão em todas as imagens do lote.
  3. removeFurniture aceita true ou on, e nada mais. Não 1, não yes. Os dois são lidos como false, e você recebe uma foto mobiliada com o sofá antigo ainda lá. Seu companheiro keepFurniture aceita texto livre nomeando o que deve ficar no lugar, até 500 caracteres, e só é lido quando removeFurniture está realmente ativo.
  4. Envie uma Idempotency-Key em toda requisição. Omiti-la é permitido e significa nenhuma proteção contra repetição, que é o padrão honesto; não vamos derivá-la da sua foto, porque então dois renders genuinamente distintos do mesmo ambiente colidiriam e o segundo chamador receberia a imagem do primeiro. Repita com o mesmo valor e uma requisição concluída é reproduzida do armazenamento com X-Stagify-Replayed: true e sem segunda cobrança. Reutilize uma com parâmetros diferentes e você recebe 422 IDEMPOTENCY_KEY_REUSED, que é a API dizendo que o seu gerador de chaves tem um bug.

Fora esses, os parâmetros que vale conhecer espelham o aplicativo web: additionalPrompt para direção em texto livre, e até cinco fotos de referência furnitureImage para o modelo se guiar.

A etiqueta de divulgação não pode ser desligada

A maioria das regras de MLS, e a orientação da NAR por trás delas, exige que uma foto de anúncio mobiliada digitalmente diga que é mobiliada digitalmente; desde janeiro de 2026, a AB 723 da Califórnia exige isso por lei. Nós escrevemos a versão estado a estado disso com detalhes.

labelVirtuallyStaged queima a divulgação nos pixels em vez de anexá-la como metadado, porque metadados são removidos por praticamente toda ferramenta pela qual uma imagem passa a caminho de um portal. Você controla a aparência: stampStyle escolhe entre dark, light, minimal e banner, stampScale dimensiona entre 0.7 e 1.6, e stampLang a renderiza em qualquer um de onze idiomas.

O que você não controla é o modo de falha. Se a etiqueta é pedida e não pode ser aplicada, a imagem é retida em vez de entregue sem etiqueta. A chamada responde 500 DISCLOSURE_STAMP_FAILED e seu crédito é estornado. Não há flag para desativar isso, nem padrão por chave que desligue em silêncio. A alternativa é entregar uma foto mobiliada sem etiqueta a um cliente que pediu explicitamente que fosse etiquetada, que é o único bug deste sistema capaz de colocar alguém diante de um regulador.

Erros, e a única regra sobre eles

Toda falha responde com um corpo JSON que traz tanto uma string error quanto um code. Ramifique pelo código, nunca pela mensagem. Mensagens são reescritas; códigos não.

Os que você vai realmente encontrar em produção são 402 INSUFFICIENT_CREDITS (recarregue; o corpo traz o saldo restante), 409 CONCURRENCY_LIMIT (renders demais em andamento numa mesma chave; a permissão padrão é três por vez, então espere um segundo e tente de novo), 409 REQUEST_IN_FLIGHT (você repetiu uma chave de idempotência que ainda está rodando, então aguarde em vez de insistir), 429 RATE_LIMITED e 422 NO_IMAGE_GENERATED, que significa que o modelo não devolveu nada e é estornado automaticamente. O limite de renders é atualmente de 60 por chave a cada cinco minutos; se você precisar de mais, ou de uma fatura em vez de cartão, escreva para nós.

Repare no padrão em todos eles: tudo que falha depois de uma cobrança é estornado. Essa invariante é a razão de a API ser síncrona e a razão de variations estar limitado a um, e é a coisa pela qual abriríamos mão de funcionalidades.

Para quem isto serve, e para quem não

A API existe para os casos em que o staging é uma etapa no pipeline de outra pessoa e não algo que alguém senta para fazer:

Ela decididamente não é para quem vai mobiliar uma casa só. Se esse é o seu caso, o aplicativo web faz mais que a API, já que o Masking Studio e o AI Designer não têm equivalente na API, e ele é gratuito, sem marca d'água. Pagar $0.15 para fazer em código o que dá para fazer de graça num navegador seria uma escolha estranha, e preferimos dizer isso a te vender um pacote de créditos.

Começando

Crie uma chave na página de chaves da API, compre o menor pacote e gaste um crédito numa foto cujo resultado você já conhece. Depois leia a referência com calma antes de escrever o laço em lote, em especial a tabela de idempotência. Vinte créditos são $3, e isso é mais que suficiente para descobrir se isto encaixa no seu pipeline.

Fontes e notas