ウェブアプリを支えるステージングエンジンが、HTTP API として利用できるようになりました。部屋の写真を送り、部屋タイプと家具スタイルを指定すると、バーチャルステージング済みの画像が同じレスポンスで返ってきます。料金は 1 枚 $0.15、サブスクリプションも最低利用量も月次請求もなく、リファレンスは stagify.ai/developers.html にあります。

この記事は、リファレンスページには書けない部分です。なぜこの API がこの形をしているのか、そして最初の午後につまずくであろういくつかの点についてです。

API 全体でエンドポイントは 1 つだけ

課金される呼び出しはちょうど 1 つです。

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 のためだけに 1 つ足すということは、ジョブが単一プロセス内にしか存在しないキューを、デプロイのたびに再起動するホスト上に持ち込むということです。それがレンダーの途中で起これば、お客様のクレジットはすでに消費されているのに、それを伝えるソケットすら残っていません。この失敗は接続が切れることより明確に悪い。切れた接続なら同じ冪等キーで再試行でき、費用もかからないからです。

そのためレンダーは完了するまで接続を保持し、数秒から数分かかります。クライアントの read timeout は 300 秒に設定してください。多くの HTTP ライブラリの既定値は 30 秒か 60 秒で、30 秒であきらめるクライアントは、この API が絶えず失敗しているように見せながら、受け取ることのないレンダーに静かにクレジットを使い続けます。

あとで変えても壊れないように作ってあります。成功レスポンスのボディにはすでに status: "succeeded" が入っており、GET /api/v1/renders/{id} もすでに存在します。将来キューを追加するとしても、それは新しい API ではなく、同じエンドポイント上の新しい status の値として現れます。succeeded 以外のステータスをすべて「GET をポーリングせよ」と解釈しておけば、クライアントはすでに将来に備えられています。

同じ理屈で variations の上限は 1 です。1 リクエストが 1 クレジット、1 画像、1 返金であり、金額のどこにも部分的なファンアウトの計算が入りません。同じ部屋を 3 通り見たいなら、リクエストを 3 本同時に投げてください。ファンアウトよりも並列性は高くなります。

プリペイドのクレジット、そしてそれがセキュリティ機能である理由

クレジットは先に購入します。レンダーが成功するたびに 1 つ消費され、失敗したレンダーは自動的に返金されるので、受け取っていない写真に支払うことはありません。パックは 20 クレジット $3 から始まり、大きいパックほど 1 枚あたりの単価が下がって、500 クレジットのパックでは 1 枚 $0.12 になります。最新の価格表は API ドキュメントページにあり、決済 webhook が課金に使うのと同じデータから描画されるため、実際の請求額とずれることはありません。

従量課金のほうが作るのは簡単でした。プリペイドがあなたにとって優れているのは、具体的に 1 点です。盗まれたキーが使えるのは、多くてもそこに残っている残高までです。月末の請求も、超過分も、漏れたキーが誰も気づかないうちに 4 桁の請求になるという筋書きもありません。ゼロで止まることは制約ではなく要点であり、すべてのレスポンスが X-Stagify-Credits-Remaining ヘッダーを返す理由でもあります。ポーリングしなくても壁が近づくのが見えます。

キーはサーバー間の利用だけを想定しています。この API は CORS ヘッダーを一切返さないため、ブラウザの JavaScript からは呼び出せません。これはいずれ直す漏れではなく意図的なものです。ブラウザから届くキーは、漏れたキーです。新しいキーは一度だけ表示し、保存するのはハッシュだけで、API キーのダッシュボードからその場で失効させられます。

つまずきやすい 4 つのこと

どの API にも、説明されれば至極まっとうで、説明される前は意味不明という挙動があります。パラメータ表に埋もれさせず、私たちのものをそのまま書きます。

  1. roomType は大文字小文字とスペースを区別し、しかも誤った値は拒否されません。この値は自由テキストとしてプロンプトに書き込まれるので、Living RoomLiving room ではありません。レンダーは返ってきますが、意図したものではなく、しかも支払い済みです。受け付ける値は BedroomLiving roomDining roomKitchenOfficeBathroomOutdoorsDorm です。自前のドロップダウンに打ち直すのではなく、GET /api/v1/options から取得してください。
  2. 認識できない furnitureStyle は黙って standard になります。同じ種類の問題で、症状はさらに静かです。スタイル名の打ち間違いはエラーにならず、バッチ内のすべての画像が既定の見た目になるだけです。
  3. removeFurniture が受け付けるのは trueon だけです。1 でも yes でもありません。どちらも false として読まれ、古いソファが残ったままのステージング写真が返ってきます。対になる keepFurniture は残すものを自由テキストで最大 500 文字まで指定でき、removeFurniture が実際に設定されているときだけ読まれます。
  4. すべてのリクエストに Idempotency-Key を付けてください。省略も許されますが、それは再送保護がないという意味であり、正直な既定値です。写真からキーを導出することはしません。そうすると同じ部屋の本当に別々な 2 つのレンダーが衝突し、2 人目の呼び出し元に 1 人目の画像が渡ってしまうからです。同じ値で再試行すれば、完了済みのリクエストがストレージから再生され、X-Stagify-Replayed: true が付いて二重課金はありません。異なるパラメータで同じ値を使い回すと 422 IDEMPOTENCY_KEY_REUSED が返り、これはキー生成器にバグがあると API が告げているということです。

それ以外で知っておく価値のあるパラメータはウェブアプリと同じです。自由テキストで指示する additionalPrompt と、モデルが参照する furnitureImage の参考写真を最大 5 枚です。

開示ラベルはオフにできません

ほとんどの MLS のルールと、その背後にある NAR のガイダンスは、ステージングされた物件写真にその旨を明示することを求めており、2026 年 1 月からはカリフォルニア州の AB 723 が法律としてこれを義務づけています。この点については州ごとにまとめた記事で詳しく書きました。

labelVirtuallyStaged は開示をメタデータとして付けるのではなく、ピクセルに焼き込みます。画像がポータルに届くまでに通るほぼすべての下流ツールがメタデータを削ぎ落とすからです。見た目は指定できます。stampStyledarklightminimalbanner から選び、stampScale は 0.7 から 1.6 の範囲で大きさを決め、stampLang は 11 言語のいずれかで表示します。

指定できないのは失敗したときの挙動です。ラベルが要求されたのに適用できない場合、ラベルなしで渡すのではなく画像を出しません。呼び出しは 500 DISCLOSURE_STAMP_FAILED を返し、クレジットは返金されます。これを無効化するフラグはなく、こっそりオフにするキー単位の既定値もありません。もう一方の道は、ラベルを明示的に求めた顧客にラベルのないステージング写真を渡すことであり、それこそがこの仕組みの中で誰かを規制当局の前に立たせかねない唯一のバグです。

エラーと、それについての 1 つのルール

あらゆる失敗は、error 文字列と code の両方を含む JSON ボディで返ります。分岐はメッセージではなくコードで行ってください。メッセージは書き換えられますが、コードは変わりません。

本番で実際に出会うのは 402 INSUFFICIENT_CREDITS(チャージしてください。ボディに残高が入ります)、409 CONCURRENCY_LIMIT(1 つのキーで同時進行中のレンダーが多すぎます。既定の上限は同時 3 件なので、1 秒下がってから再試行)、409 REQUEST_IN_FLIGHT(まだ実行中の冪等キーを再試行したので、事を大きくせず待ってください)、429 RATE_LIMITED、そしてモデルが何も返さなかったことを意味し自動的に返金される 422 NO_IMAGE_GENERATED です。レンダーのレート制限は現在キーあたり 5 分間に 60 回です。それ以上が必要な場合や、カードではなく請求書が必要な場合はメールでご連絡ください。

これらすべてに共通する型に注目してください。課金後に失敗したものはすべて返金されます。この不変条件こそ、この API が同期式である理由であり、variations が 1 に固定されている理由であり、機能を手放してでも守るものです。

誰のためのもので、誰のためのものでないか

この API は、ステージングが人が腰を据えて行う作業ではなく、誰かのパイプラインの一工程である場合のために存在します。

家 1 軒をステージングする個人には、まったく向きません。もしあなたがそれなら、ウェブアプリのほうが API より多くのことができます。Masking Studio と AI Designer に API 版はなく、しかもウェブアプリは無料でウォーターマークもありません。ブラウザで無料でできることをコードでやるために $0.15 を払うのは妙な選択で、クレジットパックを売るよりそう申し上げたいと思います。

始め方

API キーのページでキーを作り、いちばん小さいパックを買って、仕上がりがすでに分かっている写真 1 枚に 1 クレジット使ってみてください。そのうえで、バッチのループを書く前にリファレンスを、とくに冪等性の表をきちんと読んでください。20 クレジットで $3、これが自分のパイプラインに合うかどうかを確かめるには十分です。

出典と注記