AdCP 仕様ガイドライン
このドキュメントは AdCP 仕様を維持するための設計原則とルールを示します。複数のプログラミング言語で一貫性・明確さ・実装容易性を確保することが目的です。型命名の原則
型名の再利用禁止
ルール: コンセプトが異なるのに同じ enum 名やフィールド名を使わないでください(文脈が違っても不可)。 理由: TypeScript/Python/Go などの型ジェネレータは、同名で値や意味が異なると衝突し、エイリアスや深い import といった回避策を強要します。 問題の例:セマンティックなフィールド名
フィールド名は「何を表すか」を示します。汎用的なカテゴリ名は避けてください。 例:- ✅
format_category- どのチャネル/タイプのフォーマットかが明確 - ❌
type- 何のタイプか不明 - ✅
asset_content_type- アセットが含むコンテンツの種類を示します - ❌
asset_type- まだマシだが他の type フィールドと衝突しうる
Enum の統合
同じ概念が複数箇所に異なるサブセットで現れる場合:- 単一の正準 enum を作り、取りうる値をすべて含めます
- すべてのスキーマで
$refを用いて参照します - サブセットの期待値は必要に応じてフィールド説明に記載します
- 型ジェネレータが単一で一貫した型を生成します
- API は任意の値でのフィルタや指定を許容します
- 新しい値の追加は非破壊的です
- 典型的な使い方をドキュメントで明示しつつ能力を制限しません
Specialist Module Naming
Core Principle
スペシャリストモジュール名は、教えられている技術的能力 — 実践者が何を検証、解決、または運用するか — を反映しなければならず、ビジネスやマーケティングのカテゴリではありません。 「Brand」というタイトルのモジュールは曖昧です: ブランドセーフティポリシー、ブランドアイデンティティスキーマ検証、それともブランドキャンペーン戦略を教えるのか?「Brand Identity & Verification」というタイトルのモジュールは、開発者に何をできるようになるかを正確に伝えます。Naming Consistency
モジュール名は、それが現れる 4 つのすべてのサーフェスで一貫していなければなりません。- ページタイトル — モジュールの
.mdxファイルのtitle:フロントマター - バッジ —
adcp_specialist_*資格情報サフィックス(例:adcp_specialist_signals) - サイドバーナビゲーション —
sidebarTitle:フロントマター - スペシャリスト概要表 — 認定概要ページの行
Good vs. Bad Names
Naming Checklist
新しいスペシャリストモジュールを提案する前に:- 名前はビジネスドメインではなく技術的ワークフローを記述しているか?
- AdCP に不慣れな開発者が、名前だけからモジュールが何を教えるか理解できるか?
- 名前はページタイトル、バッジ、サイドバー、概要表で一貫しているか?
- バッジサフィックス(
adcp_specialist_*)は資格情報のコンテキストで自然に読めるか?
Enum 設計
Enum ファイル構成
すべての enum は/schemas/v2/enums/ に置き、意味がわかる名前にします。
Enum の命名規則
- 分類対象を説明する 名詞句 を使います
- ファイル名は kebab-case
- 生成される型名は PascalCase(AssetContentType, FormatCategory)
- 修飾子のない “type” “kind” “status” のような汎用語は避けてください
新しい Enum を作るべきとき
以下に該当する場合は専用の enum ファイルを作成します:- 値が複数スキーマで再利用されます
- 値が閉じた選択肢です
- プロトコルにとって基本的な概念です
- 型安全性が実装者の利便につながる
Enum membership — when to add a value
既存の enum に値を追加するのはキュレーションの決定であり、デフォルトではありません。enum は、実在する共有セマンティクスの厳選された名簿であり — すべてのベンダーや統合のレジストリではありません。値は、すべてが成り立つときにメンバーシップを獲得します。- 公開されている — バイヤーごとや統合ごとの形状ではなく、安定した定義を持つ外部文書化された概念を名付けている。
- ネイティブにサポートされている — 少なくとも 1 つの実在する実装者が、値ごとのビスポークマッピングなしに直接処理する(
feed_formatについては、セラーがfeed_field_mappingsなしにネイティブにパースする)。 - 共有された需要 — 複数のプロデューサーかつ複数のコンシューマーにわたって関連する(単一の双方向統合のためのブランディングではなく、共有された方言)。
custom + マッピング、または ext)を通じてモデル化します。
これは Platform Agnosticism とは異なります: feed_format 値はベンダーの公開された仕様を正当に名付けます(値が仕様そのもの)が、プラットフォーム非依存性は一般的概念のベンダー固有バージョンを禁止します。
ワークド例 — feed_format(#3456)。 tiktok_shop、pinterest_catalog、openai_product_feed は適格です: 公開され、Google Merchant Center 由来のフィード方言で、実在するセラーがネイティブにパースし、それぞれ厳格な GMC パーサーが誤処理するデルタを持ちます。公開されネイティブにパースされる仕様のないフィードは custom + feed_field_mappings を使います。
フィールド設計
判別共用体(Discriminated Unions)
オブジェクトに複数の形があり得るときは、明示的な判別フィールドを使います。過度なサブセット制限を避ける
技術的理由がない限り、リクエストスキーマで enum 値を不自然に絞り込まないでください。- ❌
asset_typesフィルタを「よく使う 7 値」に限定します - ✅ すべての asset content type を許容し、利用者が自由にフィルタできるようにします
スキーマ参照
$ref を使うとき
$ref を使うべきもの:
- enum 値(常に)
- 複数箇所で使うコアデータモデル
- 繰り返し使う複雑なネストオブジェクト
$ref を避けるもの:
- 一度きりのシンプルなインラインオブジェクト
- リクエスト固有のパラメータ
- 強い文脈依存の構造
参照パス
すべての$ref パスはスキーマルートからの絶対パスにします。
Platform Agnosticism
RULE: 規範的スキーマのフィールド名は、一般的概念の特定ベンダーバージョンを表してはなりません(MUST NOT)。プラットフォーム固有のフィールドはext.{vendor} の下に属します。
Why: AdCP はプロトコルであり、プラットフォームではありません。スキーマのトップレベルの google_campaign_id や ttd_line_id という名前のフィールドは、1 つのベンダーのデータモデルを仕様に焼き付け、ロックインを生みます。プロトコルがオープン標準として信頼できるのは、その規範的フィールドサーフェスがベンダー中立である限りにおいてです。
How: ベンダー固有のフィールドは ext.{vendor} 名前空間(スキーマ: /schemas/core/ext.json、ソース: static/schemas/source/core/ext.json)に属します。ext は additionalProperties: true です — 名前空間化は JSON スキーマではなくレビューによって強制される慣例です。
External system identifiers
正準の外部識別子空間を参照する名前は、フィールド名と enum 値の両方で正当です。区別は「ベンダートークンを含むか」ではなく「プロトコルがすでに一般的概念を持つもののそのベンダーバージョンを表すか」です。google_campaign_id(bad) — プロトコルがすでにモデル化する概念(media_buy_id)のベンダー固有 ID。ext.gamに移動。apple_podcast_id(正当) — 特定の Apple Podcasts アイテムの正準識別子。マップする一般的概念がない。Apple Podcasts 名前空間がその名前空間。nielsen_dma(正当) — 業界標準の地理区分。「Nielsen 版の地理」ではない。
- 配信プラットフォーム識別子タイプ:
distribution-identifier-type.jsonのamazon_music_id、roku_channel_id(enum 値) - フィードフォーマット:
brand.jsonのgoogle_merchant_center、facebook_catalog(enum 値) — 多くのサードパーティが実装する広く採用されたオープン交換フォーマット - 測定/データ識別子:
get-adcp-capabilities-responseのnielsen_dma(フィールド名) - プラットフォーム ID:
apple_podcast_id、apple_id(フィールド名)
ext を使う)、拒否。名前が「どの外部定義のシステム/フォーマット/識別子空間か?」を尋ねるなら(正当)、許可。フィールド名を許可するとき、tests/check-platform-agnostic.cjs の FIELD_ALLOWLIST に 1 行の正当化とともに追加します。enum 値を許可するとき、ENUM_VALUE_ALLOWLIST にパス修飾されたエントリと 1 行の正当化とともに追加します。
Reviewer checklist
- 名前が
{vendor}_{general_concept}(例:google_campaign_id、ttd_line_id)である新しいトップレベルまたはリクエスト/レスポンスフィールドを拒否。 - 外部定義のシステム、フォーマット、または識別子空間を名付ける enum 値を受け入れ。
- 例ブロック(メールアドレス、サンプル ID)のベンダー名は問題ありません。
- 不確かなとき、尋ねます: 「このフィールドまたは値はプロトコルがすでに一般的概念を持つもののあるベンダーのバージョンを表すか?」。もしそうなら、
ext.{vendor}の下に属します。
Reserved SDK-Internal Keys
RULE: トップレベルキーctx_metadata は、SDK やプラットフォームアダプターが呼び出し間で運ぶ必要があるがバイヤーが見たり依存したりしてはならない(MUST NOT)状態のアダプター内部ラウンドトリップキャッシュとして、AdCP リソースオブジェクト上で予約されています。アダプターは、ワイヤー送出前に任意のペイロードから ctx_metadata を取り除かなければなりません(MUST)。取り除き時にキーが存在し空でなかった場合、アダプターは、オペレーターがカスタムアダプターコードとの偶発的なキー衝突を検出できるよう、warning レベルのログエントリを発しなければなりません(MUST)。(空または不在の ctx_metadata はサイレント — 空でない値のみが警告をトリガー。)
Why: プラットフォームアダプター(例: Google Ad Manager、Kevel、カスタムセラーインフラ)は、アダプター内部の識別子 — GAM 広告ユニット ID、キー/バリューペア、プレースメント ID — を、バイヤー向け SDK が返す AdCP リソースに関連付ける必要があることがよくあります。参照 Prebid salesagent Python 実装は、まさにこの目的のために Product モデルの implementation_config JSON カラムを使います。予約された名前がなければ、すべての SDK が独自のもの(implementation_config、_internal、sdk_state など)を発明します。4 番目の SDK が次にそれらの 1 つと衝突するか、同じ名前に収束する 2 つの SDK が曖昧なセマンティクスを生成します。1 つの予約された名前が調整問題を除去します。
Scope: 予約は、スキーマが additionalProperties: true を宣言する AdCP リソースオブジェクト — Product、MediaBuy、Package、Creative、AudienceSegment、Signal、RightsGrant を含む — に適用されます。予約は、それが現れるどこでもリソースとともに移動します: レスポンスエンベロープのトップレベル、別のリソース内にネスト(例: MediaBuy 内の Package)、またはリソースの配列内(例: products: Product[] の各要素)。アダプターは、最も外側だけでなく、すべての出現から送出前にキーを取り除かなければなりません(MUST)。
PropertyList と CollectionList は additionalProperties: false を宣言し、フォローアップ PR がそれらのスキーマを広げるまでスコープ外です。それまで、それらのリソースのラウンドトリップ状態を必要とするアダプターは帯域外で追跡すべきです。
近隣の慣例との区別:
ext.{vendor}— ベンダー名前空間化、バイヤーに見える、ワイヤーを移動する。バイヤーが見るべきベンダー固有データに使う(例:ext.gam.line_item_id)。context/context_id— 呼び出し元がエコーする相関データ、これもワイヤーに見える。プレフィックス一致にもかかわらず、ctx_metadataはこれらのサブ名前空間ではありません — それらは無関係な概念で、異なる層を移動します。ctx_metadata— アダプター内部のみ、送出前に取り除かなければならず(MUST)、決してバイヤーに到達しません。
ctx_metadataをバイヤー可読フィールドとして推進する任意の仕様、スキーマ、または例を拒否。ctx_metadataをバイヤー向けの型付きリターンに表面化する任意の SDK 貢献を拒否。- 送出取り除き + 警告ログのパスが整っていることを条件に、
ctx_metadataをアダプター内部状態として読み書きする SDK コードを受け入れ。
破壊的変更
何が破壊的変更か
メジャーバージョンアップが必要:- enum 値の削除
- フィールド名の変更
- フィールド型の変更
- オプションを必須へ変更
- フィールドの削除
- 新しい enum 値の追加(追記のみ)
- 新しいオプションフィールドの追加
- 説明の明確化
- 新しいタスク/エンドポイントの追加
マイグレーション戦略
破壊的変更を行う場合:- v2 ディレクトリを作成:
/schemas/v2/ - v1 を維持: 旧スキーマを動作状態で残す
- マイグレーションを記載: 変更前後の例を提供します
- デプリケーション期間: 定めた期間、両バージョンをサポートします
JSON Schema Conventions
Nullable Scalars
AdCP 3.x の draft-07 スキーマでは、null 許容のスカラーフィールドを JSON スキーマの型ユニオンとしてエンコードします。nullable: true を導入しないでください。それは JSON Schema Draft 07 の一部ではなく、一貫しない SDK 投影ルールを生みます。
null 許容の enum は、type ユニオンと enum 値セットの両方に null を含めなければなりません。
type: ["string", "null"]は、フィールドが存在するときnullであってよいことを意味します。- 囲むオブジェクトの
required配列が、フィールドが存在しなければならないかを制御します。 - したがって任意の null 許容フィールドは 3 つの状態を持ちます: 省略、
nullで存在、スカラー値で存在。 - 必須の null 許容フィールドは 2 つの状態を持ちます:
nullで存在、またはスカラー値で存在。
null が異なるセマンティクスを運ぶ場合、SDK ジェネレーターがケースを潰さないよう、その区別をフィールドの説明で述べます。
スキーマのテスト
すべてのスキーマ変更は以下を満たすこと:- ✅ JSON Schema Draft 07 で検証に通る
- ✅ サンプルデータがバリデーションを通過します
- ✅ Python/TypeScript で型生成に成功します
- ✅ ドキュメントが変更に追随しています
- ✅ 変更内容を示す changeset を含めます
レビュー・チェックリスト
スキーマ変更をマージする前に確認します:- 異なるファイル間で enum 名が重複していません
- あいまいなフィールド名(素の “type” など)がない
- すべての enum が
$ref参照になっている(インラインなし) - 破壊的変更には適切なバージョニングが適用されています
- ドキュメントがスキーマに合わせて更新されています
- サンプルが新スキーマでバリデーションに通る
- 型生成テストが完了しています
- 適切なバージョンアップを含む changeset が作成されています
哲学
「スキーマこそが仕様」 ドキュメントはスキーマを反映すべきですが、真実のソースはスキーマです。ドキュメントとスキーマが乖離した場合はスキーマを優先します。つまり:- スキーマに明確で詳細な説明を書く
- 自己説明的なセマンティックな名前を使います
- バリデーションだけでなく型生成も意識して設計します
- 言語間での開発者体験を考慮します
- 判別子を使い、型チェッカーが誤りを検出できるようにします
- セマンティックな名前でコードを読みやすくします
- enum を統合し、きれいな型を生成させる
- 必要な箇所だけ制限し、過剰に縛らない
質問があるとき
スキーマ設計で迷ったら:/schemas/v2/の既存パターンを確認します- 型生成への影響を考える
- 「この名前衝突は問題を起こさないか?」と自問します
- 端的さよりも具体性を優先します
- 将来のため、このファイルに判断理由を記録します