開発 · Virtual Try-On

アプリ向けのバーチャル try-on 機能を構築する

要約

バックエンドで商品写真を受け取り、mannequin ID とともに /api/v1/tryon/apparel へ POST します。返却された generation ID をバックグラウンドワーカー(HTTPリクエストスレッドは不可)から status が completed になるまでポーリングし、output_url を自前のオブジェクトストレージに永続化して UI にレンダリングします。

  • Credits: 2Kで5 credits · 4Kで6 credits
  • 所要時間の目安: 1.5–4 min
  • 処理方式: 非同期、ポーリング

このページに記載されている数値はすべて以下に基づきます: Photta APIドキュメント · 確認済み

本番環境を見据えた実装パターン:顧客が商品写真をアップロードし、バックエンドが Photta の try-on ジョブをキューに投入、ワーカーが完了までポーリングし、フロントエンドにモデル着用結果を表示します。ブロッキングリクエストも、APIキーの漏洩も、形骸化した SDK も不要です。

RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')

仕組み

アップロードからモデル着用画像の生成まで

6つの具体的なステップ。初回インテグレーション作業の所要時間は10〜15分程度です。

  1. ステップ 1

    データフローを設計する

    顧客デバイス → 自前バックエンド(アップロード) → Photta API(ジョブ送信) → Photta API(ポーリング) → 自前データベース(ステータス同期) → 顧客デバイス(レンダリング)。最初の1行を書く前に、このフローを紙に書き出してください — 重要なのは自社バックエンドと Photta 間の境界設計だけです。

  2. ステップ 2

    APIキーを生成して保管する

    本番用キーは photta_live_ から始まります。環境変数に格納してください。絶対にブラウザへ渡してはいけません。1つのキーで Photta の全機能を利用できるため、try-on、ジュエリー、ゴーストマネキンで個別のキーを用意する必要はありません。

  3. ステップ 3

    アップロード用エンドポイントを実装する

    まずは顧客の商品写真をオブジェクトストレージに受け入れます。Photta に公開 URL を渡す方が base64 よりもシンプルで、リクエストごとの監査ログも残せます。

  4. ステップ 4

    サーバーからジョブを送信する

    商品の URL、mannequin_id、解像度、aspect_ratio を指定して /api/v1/tryon/apparel に POST します。API は 202 Accepted と generation ID を返します。クライアントが Photta ではなく自前のエンドポイントをポーリングできるよう、ID をデータベースに保存してください。

  5. ステップ 5

    バックグラウンドワーカーからポーリングする

    HTTPリクエスト内でポーリングを実行しないでください — ジョブには1.5〜4分かかり、大半のサーバーレスのタイムアウト上限を超えます。Trigger.dev、Inngest、BullMQ、Vercel Cron、または自前のキューを使用して、ジョブが完了するまで3秒ごとにポーリングを再スケジュールしてください。

  6. ステップ 6

    結果をレンダリングする

    status が completed に変わったら、data.output_url をデータベースに保存し、顧客のデバイスへリアルタイムイベント(WebSocket、SSE、またはロングポーリング)を送信します。UI のローディングスケルトンが画像に切り替われば完了です。

3つのコードサンプルでエンドツーエンドを網羅

アップロードハンドラー、ジョブディスパッチャー、ワーカー側のポーラー — 機能をリリースするために必要なすべてのコンポーネント。

// POST /app/try-on → your backend upload handler
import { put } from "@vercel/blob";

export async function uploadProduct(file: File) {
  const { url } = await put(`products/${crypto.randomUUID()}.jpg`, file, {
    access: "public",
  });
  return url;
}
const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "product_type": "top",
    "product_images": [
      "https://cdn.yourstore.com/products/shirt-front.jpg"
    ],
    "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
    "product_category": "t_shirt",
    "mannequin_id": "mnk_f3a9b1",
    "pose_id": "pose_c2d4e5",
    "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
    "lock_background": true,
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "body_size": "M",
    "expression": "neutral",
    "user_instruction": "Keep the collar pattern crisp and centered."
  }),
});
const { data } = await res.json(); // data.id, data.status
// Trigger.dev / Inngest / BullMQ task
const BASE = "https://api.photta.app/api/v1";

export const pollTryOnTask = task({
  id: "poll-try-on",
  run: async ({ generationId, attempt = 0 }) => {
    const res = await fetch(`${BASE}/tryon/apparel/${generationId}`, {
      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);
    if (attempt >= 120) throw new Error("try-on timed out");
    // Re-schedule this same task after 3 seconds.
    return ctx.retry.after(3000, { generationId, attempt: attempt + 1 });
  },
});

開発者からのよくある質問

リリース前の確認事項

バックエンドなしで実装できますか?

できません。PhottaのAPIキーは必ずサーバーサイドで保持する必要があります。薄いNext.jsのRoute HandlerやCloudflare Workerであっても、この目的においてはバックエンドとみなされます。要するに、ユーザーエージェントから読み取れない場所にキーを配置する必要があります。

HTTPリクエストのタイムアウトを防ぐにはどうすればよいですか?

リクエストハンドラー内でポーリングを行わないでください。ジョブを同期的に投入し、クライアントには生成IDを返却した上で、バックグラウンドワーカーからポーリングします。クライアントはPhottaではなく、開発者側のステータスエンドポイントをポーリングするようにします。これにより、レート制限の管理を各エンドユーザー任せにせず、自社側でコントロールできます。

実行途中でジョブが失敗した場合はどうなりますか?

Phottaは失敗時に自動でcreditsを返金し、レスポンスにはdata.error_codeとdata.error_messageが含まれます。ユーザーには分かりやすいメッセージを表示し、新しいジョブを開始する再試行ボタンを提供してください。

実際の進捗状況を表示できますか?

Phottaは中間の進捗状況を出力しません。ステータスはprocessing、completed、またはfailedのいずれかです。有効なアプローチは、1.5〜4分の所要時間枠に合わせたタイマー進行状況バーか、シンプルなスケルトンローダーを使用することです。不正確なパーセンテージを表示しないでください。

結果のURLは永続的ですか?

CDNのURLは安定していますが、プロダクトの長期的な表示をそれに依存させるべきではありません。一度バイトデータをダウンロードし、独自のS3/GCS/R2バケットに保存してください。そうすれば、PhottaのCDNが変更された場合でもPDPは動作し続けます。

バーチャルtry-onを半日で導入

このページのすべてのコードサンプルは、公式のPhottaドキュメントと一致しています。APIキーを取得すれば、コーヒーを淹れ終わる前に動くプロトタイプが完成します。