リファインメントは、プロダクト発見を対話形式に変える仕組みです。最初の brief または wholesale による発見の後、buying_mode: "refine" を使って特定のプロダクトやプロポーザルを繰り返し改善できます。選択内容の調整、変更のリクエスト、代替案の探索などを行ってから、create_media_buy で確定します。
リファインメントのライフサイクル
典型的なメディアバイのワークフローは以下のパターンに従う:
-
Discover —
buying_mode: "brief" または "wholesale" で get_products を呼び出し、マッチするインベントリを見つける。セラーはプロダクト(オプションでプロポーザルも)を返します。
-
Refine —
buying_mode: "refine" と変更リクエストの refine 配列を指定して get_products を呼び出す。各エントリはスコープと、バイヤーが求める内容を宣言します。セラーは更新された価格と設定を持つプロダクトを返します。
-
Repeat — 必要な回数だけリファインを繰り返します。各呼び出しは独立しており、ステートレスです。
-
Buy — 満足したら、
create_media_buy で最終的な選択を実行します。
リファインメントは必須ではありません。シンプルなキャンペーンは発見から購入へ直接進むことができます。ただし、複数のプロダクトを含むキャンペーン、予算配分を含むプロポーザル、または反復的な交渉が必要な場合、リファインメントこそが価値を生む場面です。
refine 配列
refine 配列は変更リクエストのリストです。各エントリは scope と、バイヤーが求める内容を宣言する:
refine 配列には少なくとも1つのエントリが必要です。セラーはレスポンスを構成する際にすべてのエントリを総合的に考慮し、refinement_applied を通じて各エントリに返答します。
各スコープは独自の id フィールドを使います——プロダクトエントリには product_id、プロポーザルエントリには proposal_id で、AdCP が他のあらゆる場所で使っている id の命名規約に合わせています。action はプロダクトおよびプロポーザルエントリでは任意で、デフォルトは "include" です。
プロダクトアクション
プロダクトスコープのエントリはアクションを宣言してもよい。省略した場合、セラーはそのエントリを "include" として扱う:
リクエストレベルの方向性
scope: "request" を使って、選択全体に対して求める内容を記述する:
セラーはこの方向性に基づいてプロダクトを追加・削除・再バランスしてもよい。refine 配列で参照されていないプロダクトも、セラーが方向性に合うと判断した場合はレスポンスに含まれることがあります。
優先順位: プロダクト個別のアクションはリクエストレベルの方向性より優先されます。リクエストレベルで「ディスプレイを減らして」と指定していても、特定のプロダクトにそれを含める明示的なアクションが設定されていれば、そのプロダクトは必ず返されます。
プロポーザルのリファインメント
プロポーザルを proposal_id で参照して、調整や削除をリクエストする。プロダクトエントリと同様に、action はデフォルトで "include" になる:
スコープの組み合わせ
すべてのスコープは組み合わせて使える。1回のリファインメント呼び出しで、選択への方向性の設定、特定プロダクトへのアクション、プロポーザルへの変更リクエストを同時に行うことができる:
セラーのレスポンス
バイヤーが refine 配列を送信すると、セラーは refinement_applied で応答します。これはバイヤーの変更リクエストと位置が一致する配列です。各エントリはリクエストが満たされたかどうかを報告する:
refinement_applied 配列は refine 配列と同じ数のエントリを同じ順序で含まなければなりません(MUST)。各エントリは、オーケストレーターがアライメントをクロスバリデーションできるよう、scope と対応する id(プロダクトスコープでは product_id、プロポーザルスコープでは proposal_id)をエコーしなければなりません(MUST)。このフィールド全体はオプションであり、リクエストごとの結果を追跡しないセラーは省略してもよい——ただし、それを返すセラーは、有効で位置が一致したエントリを返さなければなりません(MUST)。
オーケストレーターは、位置の順序だけを信頼するのではなく、エコーされた id でエントリをクロスチェックすべきだ(SHOULD)——そうでなければ、エントリを並べ替えてしまうセラーのバグが、各結果を黙って取り違えてしまいます。
よくあるリファインメントパターン
類似プロダクトを見つける
more_like_this を使って、気に入ったプロダクトに類似したプロダクトを発見します。セラーは元のプロダクトに加えて、その特性に合った追加の選択肢を返します:
フィルターを調整します
リファインリクエストのフィルターは、差分ではなく完全な目標状態を表します。適用したいフィルターセット全体を常に送信すること:
プロポーザルを絞り込む、または拡張します
プロダクトエントリは、セラーがプロポーザルに対して考慮すべきプロダクトを定義します。プロポーザルエントリと組み合わせることで、プロポーザルのプロダクトセットを絞り込んだり拡張したりできる:
Finalize は refine[] 内で排他的
action: "finalize" はバイヤーの受諾ではなくセラーのコミットです——ドラフトのプロポーザルを、確定した価格と expires_at の保留ウィンドウを持つコミット済みへ遷移させます。バイヤーは後で create_media_buy(proposal_id) を通じて、コミット済みのプロポーザルを受諾/実行します。バイヤーがファイナライズしたい場合、仕様は refine[] 配列が finalize エントリのみを含むことを要求します:
- いずれかのエントリが
action: "finalize" を持つ場合、配列内のすべてのエントリがプロポーザルスコープで action: "finalize" でなければなりません(MUST)。finalize を include / omit エントリと、またはリクエストスコープやプロダクトスコープのエントリと混在させることは、セラーによって INVALID_REQUEST で拒否されなければなりません(MUST)。
- リファインメントとコミットを近接した連続で行う必要があるバイヤーは、呼び出しを順序立てて行います: まずリファイン呼び出し(finalize なし)、次に結果の
proposal_id に対する finalize 呼び出しです。この二つの意図は別個の決定であり、仕様はそれらを別個の呼び出しとして扱います。
複数ファイナライズは観測点でアトミックです。 複数の finalize エントリが一つの呼び出しで異なるプロポーザルを対象とする場合、契約は次のとおりです: セラーは、名指しされたすべてのプロポーザルが完了しコミット済みとして永続化されていない限り、成功レスポンスを返してはなりません(MUST NOT)。コミット前のバリデーションは、いかなる副作用(インベントリのプル、条件のロック、ガバナンスのアテステーション)よりも前に実行されます。いずれかのプロポーザルがバリデーションに失敗した場合、セラーはどれもコミットせずに呼び出し全体を拒否しなければなりません(MUST)。unfinalize 操作はありません——アトミック性はコミット後の巻き戻しではなく、コミット前のバリデーションゲートで担保されます。アトミックなコミット前バリデーションを保証できないセラーは、複数ファイナライズの配列を MULTI_FINALIZE_UNSUPPORTED(推奨——クライアント側のミスではなくセラー側のケイパビリティのギャップを明示的に示します)または INVALID_REQUEST(3.1 より前のエラーカタログのセラー向けの許容されるフォールバック)で拒否しなければならず(MUST)、バイヤーはその場合、単一ファイナライズの呼び出しを順序立てて行うべきです(SHOULD)。
コミット途中の失敗(バリデーション後、永続化前)。 下流のシステムがコミット 1 とコミット 2 の間で失敗した場合——例えば、最初のシステムがすでにインベントリをロックした後に 2 番目のアドサーバーがタイムアウトした場合——セラーは、位置ごとの結果を運ぶ refinement_applied[] とともに INTERNAL_ERROR を返さなければなりません(MUST)。仕様はリカバリのパスを定義しません: バイヤーは、結果として生じる状態を未定義として扱い、リトライの前に get_media_buys / 同等の手段で再読み込みすべきです(SHOULD)。このケースからのリカバリは運用上のものであり、プロトコルが定義するものではありません。
バイヤーの意図に関する注意。 意図が明確にアトミックなコミットを必要とするバイヤー(例: 一方だけがファイナライズされると意味を成さない予算共有のプロポーザル)は、セラーが MULTI_FINALIZE_UNSUPPORTED を返した場合にその意図を放棄する用意がなければなりません(MUST)。フォールバックのパス——単一ファイナライズの呼び出しを順序立てて行うこと——は、元のアトミックな意図よりも緩いコミットの保証です。その意図の喪失に対しては、より緩い保証を受け入れるか、コミットを完全に断念する以外のリカバリはありません。複数ファイナライズのサポートを示すケイパビリティフラグはありません——失敗レスポンスが発見のためのサーフェスなので、バイヤーは最初の試行が成功するまでサポートを前提としてはなりません(MUST NOT)。
リファインモードにおけるプロポーザル
セラーはバイヤーがプロポーザルエントリを含めなかった場合でも、リファインされたプロダクトと一緒にプロポーザルを返してもよい(MAY)。例えば、3つのプロダクトをリファインしているバイヤーが、それらのプロダクトを更新された価格で受け取ると同時に、それらを組み合わせる方法を提案するプロポーザルも受け取ることがあります。
重要なポイント:
- プロポーザルは保証されない。 セラーはリファインモードでプロポーザルを生成することを要求されない。配分とキャンペーン最適化は主にオーケストレーター(バイヤーサイドエージェント)の責任です。
- リクエストレベルの ask で関心を示します。
{ "scope": "request", "ask": "suggest how to combine these products" } を含めることで、プロポーザルを歓迎することを示せる。
- 求めていないプロポーザルはリファインするか無視できます。 セラーがリクエストしていないプロポーザルを返した場合、フォローアップ呼び出しでリファインするか、単純に無視して
create_media_buy でパッケージを手動で構築することができます。
パブリッシャーは通常、バイヤーが自身でターゲティングと配分を指示する wholesale モードではプロポーザルを省略します。
ステートレス性
buying_mode: "refine" を使った各 get_products リクエストは独立しています。各リクエストの refine 配列と filters がリファインメントの意図を完全に指定します。セールスエージェントはトランスポートレベルのセッション状態(例: 前のリクエストで送信された内容を記憶すること)に依存してはなりません(MUST NOT)。
セラーは引き続き独自のプロダクトおよびプロポーザルレジストリを管理します。「ステートレス」とは、プロトコル交換が呼び出し間で暗黙的な状態を持たないことを意味します。
この設計により以下が可能になる:
- ステートレス実装 — セラーはリファインメントセッションを追跡する必要がない
- 安全なリトライ — 失敗したリファインメント呼び出しは同じパラメーターで再試行できます
- 並列探索 — オーケストレーターが複数のリファインメントパスを同時に探索できます
クライアントバリデーション
オーケストレーターはリファインメントリクエストを送信前にバリデーションすべきだ(SHOULD):
- 空でない refine —
refine 配列には少なくとも1つのエントリが必要です。空の [] はスキーマバリデーションで拒否されます。
- 有効なエントリ — 各プロダクトエントリには
scope と product_id が必要です。各プロポーザルエントリには scope と proposal_id が必要です。リクエストレベルエントリには scope と ask が必要です。action はプロダクトおよびプロポーザルエントリでは任意です(デフォルトは "include")。有効な値は、プロダクトでは include / omit / more_like_this、プロポーザルでは include / omit / finalize です。
- フィルターは絶対値 — 前のリクエストからの差分ではなく、適用したいフィルターセット全体を送信すること。
クライアント実装は送信前にリクエストスキーマに対してリファインメントリクエストをバリデーションすべきだ(SHOULD)。
エラーハンドリング
トラブルシューティング: refine[].id に対する “must NOT have additional properties”
refine[] の各スコープ分岐は additionalProperties: false です。これは、3.0-rc より前のリファインの形からの余分な id フィールドが——黙って無視されるのではなく——次のようなエラーで拒否されることを意味します:
これが見えた場合、オーケストレーターがまだ汎用の id フィールドでプロダクトまたはプロポーザルのリファインエントリを構築しています。scope: "product" の下では product_id へ、scope: "proposal" の下では proposal_id へリネームしてください。現在の形についてはタスクリファレンスを参照してください。セラー側でエコーしている場合、同じリネームが refinement_applied[] にも適用されます。
セラーのマイグレーション
refinement_applied を返すセラーには、バイヤーと並行して破壊的な作業があります:
- 各レスポンスエントリは、今や
scope を運ばなければならず、プロダクト/プロポーザルスコープについては product_id / proposal_id をエコーしなければなりません。フラットな {status, notes} エントリはレスポンススキーマによって拒否されます。
- 受信する
refine[] エントリで action が欠けている場合、エラーとしてパースするのではなく、action: "include" として扱わなければなりません。
- 3.0 リクエストスキーマに対するセラーの適合性テストは、まだ汎用の
id フィールドを使う残存したオーケストレーターのペイロードを拒否します——アップグレード後はフィクスチャのコーパスを更新してください。
規範的要件
メディアバイ仕様 はリファインメントに関して以下の規範的要件を定義している:
オーケストレーター:
buying_mode が "refine" の場合、refine を含めなければなりません(MUST)
buying_mode が "brief" または "wholesale" の場合、refine を含めてはなりません(MUST NOT)
- 各プロダクトエントリに
scope と product_id を、各プロポーザルエントリに scope と proposal_id を提供しなければなりません(MUST)
- プロダクトおよびプロポーザルエントリの
action を省略してもよい(MAY)——セラーは欠けている action を "include" として扱います
- 1つの
refine 配列に同じプロダクト ID またはプロポーザル ID を持つ複数のエントリを含めてはなりません(MUST NOT)
セールスエージェント:
action: "omit" のプロダクトをレスポンスから除外しなければなりません(MUST)
action: "omit" のプロポーザルをレスポンスから除外しなければなりません(MUST)
action: "include" のプロダクトを更新された価格で返さなければなりません(MUST)
action: "include" のプロダクトエントリの ask を満たすべきだ(SHOULD)
action: "more_like_this" のプロダクトに類似した追加のプロダクトを元のプロダクトと共に返すべきだ(SHOULD)
- レスポンスを構成する際にリクエストレベルの ask を考慮すべきだ(SHOULD)。これにより、明示的に参照されたプロダクト以外の追加プロダクトが含まれることがある(MAY)。プロダクト個別のアクションはリクエストレベルの方向性より優先されます。
action: "include" のプロポーザルエントリの ask を満たすべきだ(SHOULD)
- バイヤーが
refine を提供する場合、位置で一致した1エントリあたりの変更リクエストを持つ refinement_applied をレスポンスに含めるべきだ(SHOULD)
- バイヤーがプロポーザルエントリを含めなかった場合でもプロポーザルを返してもよい(MAY)
関連情報