웹 앱을 움직이는 스테이징 엔진을 이제 HTTP API로 쓸 수 있습니다. 방 사진을 전송하고 공간 유형과 가구 스타일을 지정하면, 스테이징된 이미지가 같은 응답으로 돌아옵니다. 이미지당 $0.15이고 구독도, 최소 사용량도, 월 청구서도 없으며, 레퍼런스 문서는 stagify.ai/developers.html에 있습니다.

이 글은 레퍼런스 페이지가 담을 수 없는 부분입니다. 이 API가 왜 지금과 같은 모양인지, 그리고 첫날 오후에 여러분의 발목을 잡을 몇 가지가 무엇인지입니다.

API 전체가 엔드포인트 하나입니다

돈이 나가는 호출은 정확히 하나뿐입니다.

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"

연동은 이것이 전부입니다. 응답에는 스테이징된 이미지가 data URL로 담기고, 상태값과 요청 id가 함께 옵니다. API의 나머지는 전부 무료이며 읽기 전용입니다. GET /api/v1/options는 모든 enum의 허용 값을 알려주고, GET /api/v1/me는 키가 유효한지 알려주며, GET /api/v1/credits는 잔액을 보고하고, GET /api/v1/renders/{id}는 특정 렌더를 다시 조회합니다.

요청은 multipart/form-data입니다. 업로드하는 것이 사진이고, JSON 본문에 base64로 넣으면 아무 대가 없이 바이트가 3분의 1 더 늘어나기 때문입니다. 이미지는 JPEG, PNG 또는 WebP로 최대 25 MB까지이며, 확인하는 것은 파일 확장자가 아니라 MIME 타입입니다.

작업 큐가 없는 이유

느린 이미지 API의 뻔한 설계는 요청을 받아 작업 id를 돌려주고 클라이언트가 결과를 폴링하게 하는 것입니다. 우리는 일부러 그렇게 하지 않았고, 그 이유는 말해 둘 가치가 있습니다. 밖에서 보면 지름길처럼 보이지만 그렇지 않기 때문입니다.

이 앱에는 작업 큐가 아예 없습니다. 오직 API만을 위해 하나를 추가한다는 것은, 작업이 단일 프로세스 안에만 존재하는 큐를, 배포할 때마다 재시작되는 호스트 위에 들이는 일입니다. 그 일이 렌더 도중에 벌어지면 고객의 크레딧은 이미 차감된 상태인데 그 사실을 알려줄 소켓조차 남아 있지 않습니다. 이 실패는 연결이 끊기는 것보다 명백히 더 나쁩니다. 끊긴 연결은 같은 멱등 키로 재시도할 수 있고 비용도 들지 않기 때문입니다.

그래서 렌더는 끝날 때까지 연결을 붙잡고 있으며, 길게는 몇 초에서 몇 분까지 걸립니다. 클라이언트의 read timeout을 300초로 설정하세요. 대부분의 HTTP 라이브러리 기본값은 30초나 60초인데, 30초에 포기하는 클라이언트는 이 API가 계속 실패하는 것처럼 보이게 만들면서 정작 받아가지도 못할 렌더에 조용히 크레딧을 씁니다.

나중에 옮기더라도 여러분 코드가 깨지지 않도록 만들었습니다. 성공 응답 본문에는 이미 status: "succeeded"가 들어 있고, GET /api/v1/renders/{id}도 이미 존재합니다. 언젠가 큐를 실제로 추가하더라도, 그것은 새 API가 아니라 같은 엔드포인트의 새로운 status 값으로 등장합니다. succeeded가 아닌 상태를 모두 “GET을 폴링하라”는 뜻으로 처리해 두면 여러분의 클라이언트는 이미 미래에 대비된 것입니다.

같은 논리로 variations의 상한은 1입니다. 요청 하나가 크레딧 하나이고 이미지 하나이며 환불 하나여서, 돈이 걸린 어디에도 부분 팬아웃 계산이 없습니다. 한 공간을 세 가지로 보고 싶다면 요청 세 개를 동시에 보내세요. 그편이 팬아웃이 주던 것보다 병렬성이 더 좋습니다.

선불 크레딧, 그리고 그것이 보안 기능인 이유

크레딧은 미리 구매합니다. 렌더가 성공할 때마다 하나씩 차감되고, 실패한 렌더는 자동으로 환불되므로 받지 못한 사진에 돈을 내는 일은 없습니다. 팩은 20크레딧 $3부터 시작하고 크기가 커질수록 이미지당 단가가 내려가, 500크레딧 팩에서는 이미지당 $0.12까지 떨어집니다. 실시간 가격표는 API 문서 페이지에 있으며, 결제 webhook이 청구할 때 쓰는 것과 같은 데이터로 렌더링되기 때문에 실제 청구액과 어긋날 수 없습니다.

종량제로 만드는 편이 더 쉬웠을 것입니다. 선불이 여러분에게 더 나은 지점은 아주 구체적으로 하나입니다. 탈취된 키가 태울 수 있는 금액은 거기 남은 잔액이 전부입니다. 월말 청구서도, 초과 요금도, 유출된 키가 누군가 알아차리기 전에 네 자릿수 청구서가 되는 시나리오도 없습니다. 0에서 딱 멈추는 것은 한계가 아니라 바로 그 요점이며, 모든 응답이 X-Stagify-Credits-Remaining 헤더를 함께 보내는 이유이기도 합니다. 폴링하지 않고도 바닥이 다가오는 것을 지켜볼 수 있습니다.

키는 서버 대 서버 용도로만 쓰도록 만들어졌습니다. 이 API는 CORS 헤더를 전혀 보내지 않으므로 브라우저 JavaScript로는 호출할 수 없고, 이는 언젠가 고칠 누락이 아니라 의도된 것입니다. 브라우저에서 닿을 수 있는 키는 이미 유출된 키입니다. 새 키는 딱 한 번만 보여주고, 해시만 저장하며, API 키 대시보드에서 즉시 폐기할 수 있습니다.

발목을 잡을 네 가지

어떤 API든 설명을 듣고 나면 지극히 합당하지만 듣기 전에는 당황스러운 동작이 있습니다. 파라미터 표에 묻어 두는 대신, 우리 것을 있는 그대로 적습니다.

  1. roomType은 대소문자와 공백을 가리며, 잘못된 값이라도 거부되지 않습니다. 이 값은 자유 텍스트로 프롬프트에 그대로 들어가므로 Living RoomLiving room이 아닙니다. 렌더는 나오지만 여러분이 의도한 결과가 아니고, 값은 이미 치른 뒤입니다. 허용되는 값은 Bedroom, Living room, Dining room, Kitchen, Office, Bathroom, Outdoors, Dorm입니다. 직접 드롭다운에 옮겨 적지 말고 GET /api/v1/options에서 가져다 쓰세요.
  2. 인식되지 않는 furnitureStyle은 조용히 standard가 됩니다. 같은 종류의 문제인데 증상은 더 조용합니다. 스타일 이름의 오타는 오류를 내지 않고, 그저 배치의 모든 이미지에 기본 스타일을 입힐 뿐입니다.
  3. removeFurnituretrue 또는 on만 받습니다. 1도 아니고 yes도 아닙니다. 둘 다 false로 읽히고, 낡은 소파가 그대로 남은 스테이징 사진을 받게 됩니다. 짝이 되는 keepFurniture는 그대로 둘 것을 자유 텍스트로 최대 500자까지 지정하며, removeFurniture가 실제로 설정되었을 때만 읽힙니다.
  4. 모든 요청에 Idempotency-Key를 보내세요. 생략해도 되지만 그러면 재전송 보호가 없다는 뜻이고, 그것이 정직한 기본값입니다. 우리는 사진에서 키를 유도하지 않습니다. 그렇게 하면 같은 방을 진짜로 따로 두 번 렌더링할 때 충돌이 나서 두 번째 호출자가 첫 번째 사람의 이미지를 받게 되기 때문입니다. 같은 값으로 재시도하면 완료된 요청이 저장소에서 재생되어 X-Stagify-Replayed: true와 함께 돌아오고 추가 과금은 없습니다. 다른 파라미터로 같은 값을 재사용하면 422 IDEMPOTENCY_KEY_REUSED가 나오는데, 이는 여러분의 키 생성기에 버그가 있다고 API가 알려주는 것입니다.

그 밖에 알아둘 만한 파라미터는 웹 앱과 같습니다. 자유 텍스트 지시를 위한 additionalPrompt, 그리고 모델이 참고할 furnitureImage 레퍼런스 사진 최대 다섯 장입니다.

고지 라벨은 끌 수 없습니다

대부분의 MLS 규정과 그 뒤에 있는 NAR 지침은 스테이징된 매물 사진에 스테이징되었다는 사실을 밝히도록 요구하며, 2026년 1월부터는 캘리포니아의 AB 723이 이를 법률로 의무화했습니다. 그 내용은 주별로 정리한 글에서 자세히 다뤘습니다.

labelVirtuallyStaged는 고지를 메타데이터로 붙이지 않고 픽셀에 직접 새깁니다. 이미지가 포털에 도달하기까지 거치는 거의 모든 하위 도구가 메타데이터를 벗겨내기 때문입니다. 모양은 여러분이 정합니다. stampStyledark, light, minimal, banner 중에서 고르고, stampScale은 0.7에서 1.6 사이로 크기를 정하며, stampLang은 열한 개 언어 중 하나로 표시합니다.

여러분이 정할 수 없는 것은 실패했을 때의 동작입니다. 라벨이 요청되었는데 적용할 수 없으면 라벨 없이 전달하는 대신 이미지를 내주지 않습니다. 호출은 500 DISCLOSURE_STAMP_FAILED로 응답하고 크레딧은 환불됩니다. 이를 끄는 플래그도 없고, 조용히 꺼두는 키별 기본값도 없습니다. 반대편 선택지는 라벨을 명시적으로 요청한 고객에게 라벨 없는 스테이징 사진을 보내는 것인데, 그것이야말로 이 시스템에서 누군가를 규제 당국 앞에 세울 수 있는 유일한 버그입니다.

오류, 그리고 오류에 관한 한 가지 규칙

모든 실패는 error 문자열과 code를 함께 담은 JSON 본문으로 응답합니다. 메시지가 아니라 코드로 분기하세요. 메시지는 다시 쓰이지만 코드는 그렇지 않습니다.

실제 운영에서 마주칠 것들은 402 INSUFFICIENT_CREDITS(충전하세요. 본문에 남은 잔액이 담깁니다), 409 CONCURRENCY_LIMIT(한 키에서 진행 중인 렌더가 너무 많습니다. 기본 허용치는 동시 세 건이므로 1초 물러났다가 재시도하세요), 409 REQUEST_IN_FLIGHT(아직 실행 중인 멱등 키를 재시도한 것이므로 문제를 키우지 말고 기다리세요), 429 RATE_LIMITED, 그리고 모델이 아무것도 반환하지 않았다는 뜻이며 자동 환불되는 422 NO_IMAGE_GENERATED입니다. 렌더 요청 한도는 현재 키당 5분에 60건입니다. 그 이상이 필요하거나 카드 대신 청구서를 원한다면 이메일로 알려주세요.

이 모두를 관통하는 패턴에 주목하세요. 과금 이후에 실패한 것은 무엇이든 환불됩니다. 이 불변 규칙이 API를 동기식으로 만든 이유이자 variations를 하나로 묶어 둔 이유이며, 기능을 포기해서라도 지킬 것입니다.

누구를 위한 것이고, 누구를 위한 것이 아닌가

이 API는 스테이징이 사람이 앉아서 하는 일이 아니라 다른 누군가의 파이프라인 속 한 단계인 경우를 위해 존재합니다.

집 한 채를 스테이징하는 개인에게는 전혀 맞지 않습니다. 그런 경우라면 웹 앱이 API보다 더 많은 일을 합니다. Masking Studio와 AI Designer는 API에 대응하는 기능이 없고, 웹 앱은 워터마크 없이 무료이기 때문입니다. 브라우저에서 공짜로 할 수 있는 일을 코드로 하려고 $0.15를 내는 것은 이상한 선택이고, 크레딧 팩을 파느니 그렇게 말하는 편을 택하겠습니다.

시작하기

API 키 페이지에서 키를 만들고, 가장 작은 팩을 사서, 결과를 이미 아는 사진 한 장에 크레딧 하나를 써보세요. 그다음 배치 루프를 작성하기 전에 레퍼런스를, 특히 멱등성 표를 제대로 읽으세요. 20크레딧이 $3이고, 이 API가 여러분의 파이프라인에 맞는지 알아보기에는 그것으로 충분합니다.

출처 및 참고