spicyapiドキュメント
ガイド
本文

被写体の入れ替え

一つのツールキットに二つの形。1 回の呼び出しで顔や頭部を入れ替えるモデルと、先にクリップを解析して入れ替える相手を選べる 2 回呼び出しのモデルがあります。

新しい場面を生成するのではなく、写っている人物を入れ替えるモデルがあります。ある写真の顔を別の写真へ移す、頭部ごと差し替える、動画クリップの中の人物を丸ごと置き換える、といったものです。カタログではこれらを toolkit: "subject-swap" という一つのラベルでまとめており、形は 2 種類あります。1 回の呼び出しで終わるものと、途中に選択を挟む 2 回呼び出しです。

それ以外に特別なところはありません。ふつうの非同期タスクであり、パラメーターはそれぞれの inputSchema が定め、結果は output.assets[] に入ります。

toolkit でこの種類のモデルを見分ける

toolkit はカタログ項目に付く分類ラベルで、認証付きの GET /api/v1/models の応答にも公開カタログにも含まれます。現在の値は subject-swap の一つだけで、どのツールキットにも属さないモデルではこのフィールド自体が存在しません。キーの有無ではなく、値で判定してください。

これは情報提供のためだけのフィールドです。どのキーがそのモデルを呼べるか、リクエストがどう課金されるか、モデルが何を受け付けるかには影響しません。見覚えのない値が来たときは、見覚えのない badges の項目と同じように無視してください。

curl -s "https://api.spicyapi.ai/api/v1/models?modality=video" \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  | jq -r '.data.items[]
      | select(.toolkit == "subject-swap" and .enabled and .available)
      | [.model, (.tasks | join(",")), .family] | @tsv'

1 行が呼び出せるエンドポイント 1 つです。このページの残りを決めるのは tasks の列です。video-analyze エンドポイントを持つ family は 2 回呼び出しの family であり、対になる video-edit エンドポイントは単独では呼び出せません。

どちらの形か見分ける

1 回呼び出し2 回呼び出し
エンドポイント編集用のエンドポイントが 1 つ同じ family 内の video-analyze、続いて video-edit
誰が入れ替わるかモデルが見つけた相手。人物を指定するフィールドはリクエストにありませんあなたが位置で選んだ相手
何を送るか対象の画像またはクリップと参照画像を、1 回のリクエストで1 回目はクリップ。2 回目は選んだ位置と、位置ごとの参照画像
支払いタスク 1 件分タスク 2 件分(別々に課金)
向いている場面画面に対象が 1 人だけのとき画面に複数の人物がいるとき、間違えて入れ替えたときの損失が大きいとき

どちらも async: true です。タスクを作成し、ポーリングまたは webhook で結果を受け取ります。

1 回呼び出し

ほかの編集モデルと変わるところはありません。エンドポイントの inputSchema を読み、値を埋めて、タスクを作成します。

const created = await fetch('https://api.spicyapi.ai/api/v1/jobs/createTask', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPICY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'order-8814-swap-1',
  },
  body: JSON.stringify({
    model: swap.model, // 上のカタログ照会で得た項目の一つ
    input: {
      image_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
      reference_image_url: 'spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m',
    },
  }),
});

フィールド名はエンドポイントごとに異なります。prompt や seed、出力形式を受け取るものもあれば、2 枚の画像しか受け取らないものもあります。何を送れるかはスキーマがすべてです。あるエンドポイントのフィールド名を別のエンドポイントへ持ち込まないでください。参照画像には、公開された HTTPS URL、小さな画像ならインラインの Base64、アップロード済みの spicy:// URI が使えます。メディアを参照してください。

2 回呼び出し

1 回目の呼び出しはクリップを見て、何を見つけたかを順序の付いたプレビュー静止画として返します。そのうちどれを入れ替えるかを決めるのはあなたです。実際の作業を行うのは 2 回目の呼び出しです。

あなたのクリップ ──▶ video-analyze ──▶ output.assets[0], [1], [2] …(対象 1 件につき静止画 1 枚)
                                             │
                       位置 0 と 2 を選ぶ ──▶│
                                             ▼
             video-edit(analysis_task_id + swap_targets)──▶ 完成したクリップ

なぜ 1 回で済ませないのか。4 人が写っている場面では、1 回呼び出しのモデルはあなたがどの人物を指しているかを推測するしかなく、推測を外せば、別人が入れ替わった動画が完成し、そのまま課金されます。作業を分ければ、選択は明示的になり、目で確かめられるようになります。

クリップを解析する

video-analyze エンドポイントにクリップを送ります。エンドポイントに resolution があるなら、明示的に指定してください。2 回目の呼び出しも同じ値で実行する必要があるため、完成したクリップに持たせたい解像度をここで選びます。これらのエンドポイントでは、解析の料金は解像度によって変わりません。

const analysis = await createTask({
  model: analyze.model, // tasks に "video-analyze" を含む項目
  input: {
    video_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
    resolution: '720p',
  },
});
const analysisTaskId = analysis.taskId;

何が見つかったかを読む

いつもどおりタスクの完了を待ちます。成功した解析は、追跡した対象 1 件につき 1 枚のプレビュー静止画を output.assets[] に返します。

{
  "state": "succeeded",
  "output": {
    "assets": [
      { "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" },
      { "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" }
    ]
  },
  "settled": true
}

この配列の順序が契約です。 各要素の添字が、2 回目の呼び出しで送り返す object_index になります。1 枚目が 0、2 枚目が 1、以下同様です。選ぶ人に静止画を見せてもよいし、自分で突き合わせてもかまいませんが、いずれにしても送り返す位置はこの同じ配列のものにしてください。

追跡できる対象が見つからなかった解析は、空の配列で成功したりはしません。errorCode: "generation_failed" とともに failed で終わり、仮押さえは全額解放されます。つまり succeeded の解析には必ず静止画が 1 枚以上あり、コードが扱うべきなのは「どれを選ぶか」だけです。

選んだ相手を入れ替える

解析タスクの ID と、入れ替えたい対象 1 件につき 1 つの swap_targets 要素を送ります。挙げなかった対象はそのまま残ります。

const swapped = await createTask({
  model: edit.model, // 同じ family の "video-edit" の項目
  input: {
    analysis_task_id: analysisTaskId,
    resolution: '720p', // 解析を実行したときと同じ値
    swap_targets: [
      { object_index: 0, image_urls: ['spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m'] },
    ],
  },
});

結果は output.assets[] に入るクリップ 1 本で、ほかの動画タスクと同じです。

2 回目の呼び出しが受け付ける前に確認すること

これらの確認は作成時に行われるため、拒否されたリクエストは何も作らず、何も仮押さえしません。

ルール理由
解析はあなたのアカウントのもので、かつ成功している必要があります実行待ちの解析や失敗した解析には、指し示す先がありません
2 回とも同じ API キーを使ってください解析は作成に使ったキーでしか読み取れないため、別のキーでは選ぶ元の静止画そのものが見えません
すべての object_index は範囲内で、かつ1 回だけ現れる必要があります範囲外なら黙って別人が入れ替わり、重複すれば同じ相手が 2 回入れ替わります。どちらも通常どおり完了し、通常どおり課金されます
resolution のような料金に影響するフィールドは、解析と一致していなければなりません引き継いでしまうと、どの料金区分で課金されているかが見えなくなります
元のクリップは同じものである必要がありますvideo_url を省略すれば解析したクリップが使われます。指定するなら完全に一致していなければなりません
解析は完了してから 24 時間使えますそれを過ぎると裏側の追跡データは失われ、元のクリップも多くの場合残っていません

`resolution` は両方の呼び出しで指定するか、既定値をよく確かめる

省略されたフィールドは、2 つのリクエストを比べる前にスキーマの default の値になります。したがって、どちらも既定値のままの解析と入れ替えは一致します。しかし、値を指定した解析と、空のままにした入れ替えは一致しません。この食い違いは黙って直されるのではなく、拒否されます。両方の呼び出しで値を明示的に送れば、この問題そのものがなくなります。

2 回目の呼び出しが拒否されたとき

いずれも HTTP 400 で、業務コードも code: 400 です。msg は次のいずれかになります。

msg何が起きたか対処
The referenced analysis task was not found.このアカウントにそのタスクがない、または analysis_task_id がタスク ID ではありません自分の解析が返した taskId を使ってください。別アカウントのタスクも、意図的に同じ返し方になります
The referenced task belongs to a different model.ID が別のタスクを指しています。多くは入れ替えタスク自身か、別の family の解析です同じ family の video-analyze タスクを指してください
The referenced analysis did not complete successfully.解析がまだ実行中か、failed / expired で終わっています完了を待つか、実行し直してください
That analysis has no reusable result. Run the analysis step again.解析は成功したものの、入れ替えに使えるものが残っていません同じクリップで解析をやり直してください
That analysis result has expired. Run the analysis step again.24 時間を超えています解析をやり直してください
"resolution" must match the analysis step.料金に影響するフィールドが 2 回の呼び出しで食い違っています解析の値を送るか、望む値で解析をやり直してください
"video_url" must match the analysis step.別のクリップを送っています同じ URI を送るか、このフィールドを省いてください
That analysis found N target(s); "object_index" is out of range.解析が返した数を超える位置を指定しています位置は 0 から始まり、assets.length - 1 で終わります
"object_index" is listed more than once.2 つの要素が同じ相手を指しています相手 1 人につき要素は 1 つにし、その相手の参照画像はすべてその要素の image_urls にまとめてください

2 回の呼び出しの料金

解析と入れ替えはそれぞれ別の料金を持つ 2 つのタスクで、仮押さえも精算も別々に行われます。2 回目が拒否されたときは料金がかかりません。解析が成功していれば、そのあと入れ替えをやめても解析分は課金されます。

  • 解析はふつう per_request です。クリップの長さにも、見つかった対象の数にもよらず、1 回の呼び出しにつき 1 件の課金です。
  • 入れ替えはふつう per_second で、まとまった単位(ブロック)ごとに数えます。長さはあなたが指定するフィールドではなくクリップ自体から決まるため、事前に提示される金額はそのエンドポイントが受け付ける最長のクリップを前提にした金額になります。提示された金額がそのまま仮押さえ額になり、最終的な請求額は切り上げた長さから計算され、仮押さえ額を超えることはありません。差額はタスクの精算時に残高へ戻ります。ここで仮押さえ額が最終請求額より大きく見えるのは、想定どおりの挙動です。予算を組む前に課金を読んでください。

どちらの料金も推測せずカタログから読み、正確な金額が必要なら呼び出しごとに見積もりを取ってください。

次のステップ

このページの内容