> ## Documentation Index
> Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# sync_audiences

> sync_audiences タスク — ハッシュ化されたファーストパーティ CRM オーディエンスを AdCP セラーアカウントにアップロードして、リターゲティング、サプレッション、類似オーディエンス拡張に活用します。マッチングステータスのトラッキングをサポート。

セラーアカウント上のファーストパーティ CRM オーディエンスを管理します。ハッシュ化された顧客リストをアップロードし、マッチングステータスを確認し、`create_media_buy` のターゲティングオーバーレイで結果のオーディエンスを参照して、明示的なリターゲティングやサプレッションに利用できます。

オーディエンスは[シグナル](/docs/signals/overview)とは異なります。シグナルは、プロダクトのシグナルオプション、プロバイダーが公開するシグナル定義、または `get_signals` を通じて発見される名前付きのターゲティング次元です。オーディエンスは自社が所有してアップロードするデータです。`audience_include` を使うとアップロードしたリストのメンバーだけをターゲットにできます。`audience_include` はハード制約であり、リスト上のユーザーのみが対象となります。オーディエンスに*類似した*新規ユーザーを探す場合（類似オーディエンス拡張）は、キャンペーンブリーフにそのインテントを記述する — 拡張戦略はセラーが担当します。なお、ブリーフで表明された類似インテントはプロトコルを通じて検証できないため、セラー側のレポートで確認すること。

**レスポンス時間**: アップロードは約 1〜2 秒で受け付けられます。オーディエンスごとのマッチングは非同期です——マッチングが完了するまでタスクはアクティブな状態が続く（セラーによって 1〜48 時間）。オーディエンスの準備ができたときに Webhook を受け取るには `push_notification_config` を設定すること。取り込みパイプラインが同期レスポンスでオーディエンスごとの結果を返せないセラー（バッチ取り込み、ガバナンスによるゲート付きアップロード、クリーンルームのフロー）は、操作レベルの submitted タスクエンベロープで応答してもよい（MAY）——[レスポンスの形](#レスポンスの形)を参照。

**リクエストスキーマ**: [`/schemas/v3/media-buy/sync-audiences-request.json`](https://adcontextprotocol.org/schemas/v3/media-buy/sync-audiences-request.json)
**レスポンススキーマ**: [`/schemas/v3/media-buy/sync-audiences-response.json`](https://adcontextprotocol.org/schemas/v3/media-buy/sync-audiences-response.json)

## クイックスタート

顧客リストをアップロードしてステータスを確認します:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from "@adcp/sdk/testing";
  import { SyncAudiencesResponseSchema } from "@adcp/sdk";
  import { createHash } from "crypto";

  const hashEmail = (email) =>
    createHash("sha256").update(email.toLowerCase().trim()).digest("hex");

  const hashPhone = (e164Phone) =>
    createHash("sha256").update(e164Phone).digest("hex");

  const result = await testAgent.syncAudiences({
    account: { account_id: "acct_12345" },
    audiences: [
      {
        audience_id: "existing_customers",
        name: "Existing customers",
        add: [
          { external_id: "crm_1001", hashed_email: hashEmail("alice@example.com") },
          { external_id: "crm_1002", hashed_email: hashEmail("bob@example.com"), hashed_phone: hashPhone("+12065551234") },
        ],
      },
    ],
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  const validated = SyncAudiencesResponseSchema.parse(result.data);

  // Three-shape discriminated union: errors | submitted | audiences
  if ("status" in validated && validated.status === "submitted") {
    // Whole sync queued asynchronously — poll tasks/get with task_id or await webhook
    console.log(`Sync queued as task ${validated.task_id}: ${validated.message ?? ""}`);
  } else if ("errors" in validated && validated.errors && !("audiences" in validated)) {
    throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
  } else if ("audiences" in validated) {
    for (const audience of validated.audiences) {
      console.log(`${audience.audience_id}: ${audience.action} (${audience.status ?? "n/a"})`);
      if (audience.status === "ready") {
        console.log(`  Matched ${audience.matched_count} of ${audience.uploaded_count} members (this sync)`);
      }
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  import hashlib
  from adcp.testing import test_agent

  def hash_email(email: str) -> str:
      return hashlib.sha256(email.lower().strip().encode()).hexdigest()

  def hash_phone(e164_phone: str) -> str:
      return hashlib.sha256(e164_phone.encode()).hexdigest()

  async def main():
      result = await test_agent.simple.sync_audiences(
          account={'account_id': 'acct_12345'},
          audiences=[{
              'audience_id': 'existing_customers',
              'name': 'Existing customers',
              'add': [
                  {'external_id': 'crm_1001', 'hashed_email': hash_email('alice@example.com')},
                  {'external_id': 'crm_1002', 'hashed_email': hash_email('bob@example.com'), 'hashed_phone': hash_phone('+12065551234')},
              ]
          }]
      )

      # Three-shape discriminated union: errors | submitted | audiences
      if getattr(result, 'status', None) == 'submitted':
          # Whole sync queued asynchronously — poll tasks/get with task_id or await webhook
          print(f"Sync queued as task {result.task_id}: {getattr(result, 'message', '') or ''}")
          return

      if getattr(result, 'errors', None) and not getattr(result, 'audiences', None):
          raise Exception(f"Operation failed: {result.errors}")

      for audience in result.audiences:
          status = getattr(audience, 'status', 'n/a')
          print(f"{audience.audience_id}: {audience.action} ({status})")
          if status == 'ready':
              print(f"  Matched {audience.matched_count} of {audience.uploaded_count} members (this sync)")

  asyncio.run(main())
  ```
</CodeGroup>

## リクエストパラメータ

| パラメータ            | 型                                                                                | 必須  | 説明                                                                                                                                                   |
| ---------------- | -------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account`        | [account-ref](/docs/building/by-layer/L2/accounts-and-agents#account-references) | はい  | アカウント参照。`{ "account_id": "..." }` を渡すか、セラーが暗黙的な解決をサポートしている場合は `{ "brand": {...}, "operator": "..." }` を渡します。                                         |
| `audiences`      | [Audience](#audience-object)\[]                                                  | いいえ | 同期するオーディエンス。省略した場合、呼び出しはディスカバリー専用となり、変更なしで既存のすべてのオーディエンスを返します。                                                                                       |
| `delete_missing` | boolean                                                                          | いいえ | true の場合、このリクエストに含まれていないアカウント上のバイヤー管理オーディエンスを削除する（デフォルト: false）。セラー管理のオーディエンスには影響しません。`audiences` 配列を省略した状態と組み合わせると、すべてのバイヤー管理オーディエンスが削除されるため注意すること。 |

### Audience オブジェクト

| フィールド           | 型                                     | 必須  | 説明                                                                                             |
| --------------- | ------------------------------------- | --- | ---------------------------------------------------------------------------------------------- |
| `audience_id`   | string                                | はい  | このオーディエンスのバイヤー識別子。ターゲティングオーバーレイでオーディエンスを参照するために使用します。                                          |
| `name`          | string                                | いいえ | 人間が読みやすい名前                                                                                     |
| `delete`        | boolean                               | いいえ | true の場合、このオーディエンスをアカウントから完全に削除します。その他のフィールドはすべて無視されます。                                        |
| `add`           | [AudienceMember](#audience-member)\[] | いいえ | このオーディエンスに追加するメンバー                                                                             |
| `remove`        | [AudienceMember](#audience-member)\[] | いいえ | このオーディエンスから削除するメンバー。同じ識別子が `add` と `remove` の両方に現れた場合、remove が優先されます。                          |
| `consent_basis` | string                                | いいえ | GDPR の適法根拠: `consent`、`legitimate_interest`、`contract`、または `legal_obligation`。規制対象市場の一部セラーで必須。 |

### Audience メンバー

すべてのメンバーには `external_id`（バイヤーが割り当てた安定した識別子）と、少なくとも 1 つのマッチング可能な識別子が必要です。送信前にすべての値を SHA-256 でハッシュ化すること — メールアドレスは小文字化+トリム、電話番号は E.164 形式（例: `+12065551234`）に正規化します。

| フィールド          | 型      | 説明                                                                                            |
| -------------- | ------ | --------------------------------------------------------------------------------------------- |
| `external_id`  | string | **必須。** このメンバーのバイヤーが割り当てた安定した識別子（例: CRM レコード ID、ロイヤルティ ID）。重複排除、削除、バイヤーシステムとのクロスリファレンスに使用します。 |
| `hashed_email` | string | 小文字化・トリムされたメールアドレスの SHA-256 ハッシュ（64 文字の16進数）                                                  |
| `hashed_phone` | string | E.164 形式の電話番号の SHA-256 ハッシュ（64 文字の16進数）                                                       |
| `uids`         | UID\[] | ユニバーサル ID: `type`（rampid、uid2、maid など）+ `value`                                               |

同一人物に複数の識別子を提供するとマッチ率が向上します。複合識別子（例: ハッシュ化された姓名 + 郵便番号）はまだ標準化されていない — プラットフォーム固有の拡張には `ext` を使用すること。

**識別子のサポートはセラーによって異なる**: 送信前に `get_adcp_capabilities` → `media_buy.audience_targeting.supported_identifier_types` および `media_buy.audience_targeting.supported_uid_types` を確認すること。MAID のサポートは全セラーに共通ではない（LinkedIn は MAID を受け付けない。iOS の IDFA には App Tracking Transparency の同意が必要）。ケイパビリティの `media_buy.audience_targeting.matching_latency_hours` の範囲と `media_buy.audience_targeting.minimum_audience_size` もセラー固有の値です。

**サイズ制限**: ペイロードはすべてのオーディエンスを合わせて 1 回の呼び出しにつき最大 100,000 メンバーに制限されます。より大きなリストの場合は、`add` のデルタを使って順次呼び出しに分割すること。

**同時実行**: `sync_audience` への呼び出しは互いに独立していることを確認すること。処理は順不同になる場合があります。順次実行が必要な場合は、設定した Webhook へのコールバックを受け取ってから次の呼び出しを行うこと。

## レスポンスの形

レスポンスは識別共用体を使います——レスポンスは三つの形のうちちょうど一つを持ち、混在することはありません:

**1. 同期的な成功** — オーディエンスごとの結果:

* `audiences` — このリクエストに含まれていないオーディエンスも含む、アカウント上のすべてのオーディエンスの結果
* `sandbox` — このレスポンスがサンドボックスモードからのものかを示すブール値（オプション）

**2. 終端の失敗** — 処理されたオーディエンスなし:

* `errors` — 操作レベルのエラーの配列（認証失敗、アカウントが見つからない、無効なリクエスト形式）

**3. Submitted タスクエンベロープ** — 操作全体が非同期でキューに入れられた（バッチ取り込み、ガバナンスによるゲート付きアップロード、レスポンスが出る前にセラーがオーディエンスごとの結果を返せないクリーンルームのフロー）:

* `status` — 常に `"submitted"`
* `task_id` — `tasks/get` によるポーリングまたは完了時のウェブフック受信のためのハンドル
* `message` — キューの状態を説明する任意の人が読めるテキスト

最終的なオーディエンスごとの `audiences` 配列は、submitted エンベロープではなくタスクの完了アーティファクトに載ります。オーディエンスごとの非同期マッチング（同期の残りが解決される間、一つのオーディエンスが `processing` になっている）は、submitted エンベロープではなく、その項目に `status: "processing"` を持つ同期的な成功の分岐に属します。オーディエンスごとの [audience-status](#オーディエンスステータス) 列挙上のマッチングレイテンシが一般的なケースであり、submitted エンベロープはより少ない操作レベルの非同期のケース向けです。

**成功レスポンスの各オーディエンスに含まれるフィールド:**

| フィールド                  | 説明                                                                                                                                      |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `audience_id`          | リクエストから返されるバイヤーの識別子                                                                                                                     |
| `seller_id`            | セラーの広告プラットフォームで割り当てられた ID                                                                                                               |
| `action`               | `created`、`updated`、`unchanged`、`deleted`、または `failed`                                                                                  |
| `status`               | `processing`、`ready`、または `too_small`。action が `created`、`updated`、または `unchanged` の場合に存在します。action が `deleted` または `failed` の場合は存在しません。 |
| `uploaded_count`       | この同期操作で送信されたメンバー数（差分、累積ではない）。ディスカバリー専用呼び出しでは 0。                                                                                         |
| `total_uploaded_count` | すべての同期にわたってアップロードされたメンバーの累積数。`matched_count` と比較してマッチ率を計算します。                                                                           |
| `matched_count`        | すべての同期にわたってプラットフォームユーザーにマッチしたメンバーの合計数（累積）。`status: "ready"` の時に設定されます。                                                                  |
| `effective_match_rate` | すべての識別子タイプにわたる重複排除済みのマッチ率（0〜1）。リーチ推定のための単一の数値。`status: "ready"` の時に設定されます。                                                              |
| `match_breakdown`      | 識別子タイプ別のマッチ結果。どの ID タイプがどのマッチ率で解決されるかを示します。[マッチ内訳](#マッチ内訳)を参照。                                                                          |
| `last_synced_at`       | 最新の同期の ISO 8601 タイムスタンプ。セラーがこれをトラッキングしていない場合は省略されます。                                                                                    |
| `minimum_size`         | このプラットフォームでターゲティングするための最小マッチオーディエンスサイズ。`status: "too_small"` の時に設定されます。                                                                 |
| `errors`               | オーディエンスごとのエラー（`action: "failed"` の場合のみ）                                                                                                 |

## マッチ内訳

セラーが識別子タイプ別のレポートをサポートしている場合、レスポンスには `match_breakdown` が含まれる — これはどの ID タイプが解決されているか、どのマッチ率かを示す配列です。バイヤーは将来のアップロードでどの識別子を優先すべきかを判断するために活用できます。

```json theme={null}
{
  "audience_id": "existing_customers",
  "action": "updated",
  "status": "ready",
  "uploaded_count": 5000,
  "total_uploaded_count": 25000,
  "matched_count": 18750,
  "effective_match_rate": 0.75,
  "match_breakdown": [
    { "id_type": "hashed_email", "submitted": 25000, "matched": 17500, "match_rate": 0.70 },
    { "id_type": "hashed_phone", "submitted": 15000, "matched": 12000, "match_rate": 0.80 },
    { "id_type": "rampid", "submitted": 8000, "matched": 7200, "match_rate": 0.90 }
  ]
}
```

主要なセマンティクス:

* **`submitted` と `matched` は累積値**であり、すべての同期にわたる値で、`total_uploaded_count` のセマンティクス（`uploaded_count` ではない）に対応します。
* **`effective_match_rate` は重複排除済み** — メールと電話の両方でマッチしたメンバーは 1 回としてカウントされます。タイプ別マッチ率の合計以下になります。
* **`match_rate` はサーバーが権威のある値** — コンシューマーは submitted/matched から自分で計算するよりもこの値を優先すべきです。
* **`id_type` の値**は、ハッシュ化された PII タイプ（`hashed_email`、`hashed_phone`）とユニバーサル ID タイプ（`rampid`、`uid2`、`id5`、`euid`、`pairid`、`maid`）を組み合わせたものです。

集計マッチカウントのみをサポートするセラーは `match_breakdown` を完全に省略します。

## よくあるシナリオ

### ディスカバリー専用

変更なしで既存のすべてのオーディエンスのステータスを確認します。レスポンスにはアカウント上のすべてのオーディエンスが含まれる — `audience_id` でフィルタリングして目的のオーディエンスを見つけること:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from "@adcp/sdk/testing";
  import { SyncAudiencesResponseSchema } from "@adcp/sdk";

  const result = await testAgent.syncAudiences({
    account: { account_id: "acct_12345" },
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  const validated = SyncAudiencesResponseSchema.parse(result.data);

  if ("errors" in validated && validated.errors) {
    throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
  }

  if ("audiences" in validated) {
    for (const audience of validated.audiences) {
      console.log(`${audience.audience_id}: ${audience.status ?? "n/a"}`);
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def main():
      result = await test_agent.simple.sync_audiences(account={'account_id': 'acct_12345'})

      if hasattr(result, 'errors') and result.errors:
          raise Exception(f"Operation failed: {result.errors}")

      for audience in result.audiences:
          status = getattr(audience, 'status', 'n/a')
          print(f"{audience.audience_id}: {status}")

  asyncio.run(main())
  ```
</CodeGroup>

### サプレッションリスト

新規獲得キャンペーンから除外するために、既存顧客のリストをアップロードする:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from "@adcp/sdk/testing";
  import { SyncAudiencesResponseSchema } from "@adcp/sdk";
  import { createHash } from "crypto";

  const hashEmail = (email) =>
    createHash("sha256").update(email.toLowerCase().trim()).digest("hex");

  // CRM エクスポートからのハッシュ化された顧客メールアドレス
  const existingCustomers = [
    { hashed_email: hashEmail("customer1@example.com") },
    { hashed_email: hashEmail("customer2@example.com") },
  ];

  const result = await testAgent.syncAudiences({
    account: { account_id: "acct_12345" },
    audiences: [
      {
        audience_id: "existing_customers",
        name: "Existing customers — suppression",
        add: existingCustomers,
      },
    ],
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  const validated = SyncAudiencesResponseSchema.parse(result.data);

  if ("errors" in validated && validated.errors) {
    throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
  }

  if ("audiences" in validated) {
    const audience = validated.audiences[0];
    console.log(`Status: ${audience.status}`);
    // ready になったら、create_media_buy の targeting_overlay.audience_exclude で audience_id を参照する
  }
  ```

  ```python Python theme={null}
  import asyncio
  import hashlib
  from adcp.testing import test_agent

  def hash_email(email: str) -> str:
      return hashlib.sha256(email.lower().strip().encode()).hexdigest()

  async def main():
      existing_customers = [
          {'hashed_email': hash_email('customer1@example.com')},
          {'hashed_email': hash_email('customer2@example.com')},
      ]

      result = await test_agent.simple.sync_audiences(
          account={'account_id': 'acct_12345'},
          audiences=[{
              'audience_id': 'existing_customers',
              'name': 'Existing customers — suppression',
              'add': existing_customers
          }]
      )

      if hasattr(result, 'errors') and result.errors:
          raise Exception(f"Operation failed: {result.errors}")

      audience = result.audiences[0]
      print(f"Status: {audience.status}")
      # ready になったら、create_media_buy の targeting_overlay.audience_exclude で audience_id を参照する

  asyncio.run(main())
  ```
</CodeGroup>

### メンバーの削除

オーディエンスを差分で更新する — 新しいメンバーを追加し、対象外になったメンバーを削除します:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from "@adcp/sdk/testing";
  import { SyncAudiencesResponseSchema } from "@adcp/sdk";
  import { createHash } from "crypto";

  const hashEmail = (email) =>
    createHash("sha256").update(email.toLowerCase().trim()).digest("hex");

  const result = await testAgent.syncAudiences({
    account: { account_id: "acct_12345" },
    audiences: [
      {
        audience_id: "lapsed_subscribers",
        name: "Lapsed subscribers",
        add: [{ hashed_email: hashEmail("newlapse@example.com") }],
        remove: [{ hashed_email: hashEmail("reactivated@example.com") }],
      },
    ],
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  const validated = SyncAudiencesResponseSchema.parse(result.data);

  if ("errors" in validated && validated.errors) {
    throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
  }

  if ("audiences" in validated) {
    for (const audience of validated.audiences) {
      console.log(`${audience.audience_id}: ${audience.action}`);
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  import hashlib
  from adcp.testing import test_agent

  def hash_email(email: str) -> str:
      return hashlib.sha256(email.lower().strip().encode()).hexdigest()

  async def main():
      result = await test_agent.simple.sync_audiences(
          account={'account_id': 'acct_12345'},
          audiences=[{
              'audience_id': 'lapsed_subscribers',
              'name': 'Lapsed subscribers',
              'add': [{'hashed_email': hash_email('newlapse@example.com')}],
              'remove': [{'hashed_email': hash_email('reactivated@example.com')}]
          }]
      )

      if hasattr(result, 'errors') and result.errors:
          raise Exception(f"Operation failed: {result.errors}")

      for audience in result.audiences:
          print(f"{audience.audience_id}: {audience.action}")

  asyncio.run(main())
  ```
</CodeGroup>

### オーディエンスの削除

他のオーディエンスに影響を与えずに特定のオーディエンスをアカウントから削除します。オーディエンスオブジェクトに `delete: true` を設定します:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from "@adcp/sdk/testing";
  import { SyncAudiencesResponseSchema } from "@adcp/sdk";

  const result = await testAgent.syncAudiences({
    account: { account_id: "acct_12345" },
    audiences: [
      { audience_id: "old_campaign_list", delete: true },
    ],
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  const validated = SyncAudiencesResponseSchema.parse(result.data);

  if ("audiences" in validated) {
    const audience = validated.audiences.find(a => a.audience_id === "old_campaign_list");
    console.log(`${audience.audience_id}: ${audience.action}`); // "deleted"
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def main():
      result = await test_agent.simple.sync_audiences(
          account={'account_id': 'acct_12345'},
          audiences=[{'audience_id': 'old_campaign_list', 'delete': True}]
      )

      if hasattr(result, 'errors') and result.errors:
          raise Exception(f"Operation failed: {result.errors}")

      audience = next(a for a in result.audiences if a.audience_id == 'old_campaign_list')
      print(f"{audience.audience_id}: {audience.action}")  # "deleted"

  asyncio.run(main())
  ```
</CodeGroup>

1 回の呼び出しで複数のオーディエンスを削除するには、それぞれに `delete: true` を指定します。すべてのバイヤー管理オーディエンスを一度に削除するには、空の `audiences` 配列と `delete_missing: true` を使用する — ただし、すべてが削除されるため注意すること。

### メディアバイでのオーディエンスの使用

オーディエンスが `ready` になったら、`create_media_buy` のターゲティングオーバーレイで `audience_id` を参照します。オーディエンス ID はセラーアカウントにスコープされるため、セラーをまたいで使用することはできません。

```json test=false theme={null}
{
  "brand": { "house_domain": "acme.com", "brand_id": "main" },
  "start_time": "asap",
  "end_time": "2026-03-31T23:59:59Z",
  "packages": [
    {
      "product_id": "prod_sponsored_content",
      "pricing_option_id": "cpm_standard",
      "budget": 10000,
      "targeting_overlay": {
        "audience_include": ["high_value_prospects"],
        "audience_exclude": ["existing_customers"]
      }
    }
  ]
}
```

## オーディエンスステータス

プラットフォームのマッチングは非同期です。`status` フィールドは現在の状態を反映する:

| ステータス        | 意味                                                                                       |
| ------------ | ---------------------------------------------------------------------------------------- |
| `processing` | プラットフォームがアップロードされたメンバーをユーザーベースと照合中。後でもう一度確認すること — まだキャンペーンを作成してはいけない。                    |
| `ready`      | オーディエンスはターゲティングに使用可能。`matched_count` が設定されています。                                          |
| `too_small`  | マッチしたオーディエンスがプラットフォームの最小サイズを下回っています。レスポンスの `minimum_size` でしきい値を確認できます。メンバーを追加して再同期すること。 |

`status` は `action` が `created`、`updated`、または `unchanged` の場合に存在します。`action` が `deleted` または `failed` の場合は存在しません。

セラーは、`matched_count < minimum_size` の場合には常に `too_small` を出力しなければなりません（MUST）。プラットフォームの最小値を下回る `matched_count` で `ready` を返すことは非準拠です——バイヤーは、カウントの事後的な解釈ではなく、ターゲティングが失敗するというプログラム的なシグナルとしてステータス値に依拠します。

**Webhook（推奨）**: アップロード前にプロトコルレベルで `push_notification_config` を設定すること。タスクはセラーのプラットフォームがメンバーをマッチングしている間アクティブな状態が続く。マッチングが完了すると、タスクが完了し、最終結果（`status: "ready"` または `status: "too_small"`）とともに Webhook が発火します。現実的な期待値を設定するには `get_adcp_capabilities` → `audience_targeting.matching_latency_hours` を確認すること（通常 1〜48 時間）。

**ポーリングフォールバック**: Webhook を使用しない場合は、`audiences` を省略したディスカバリー専用呼び出しで 15 分以上の間隔でポーリングすること。タスクのステータスを確認するには `tasks/get` と `task_id` を使用する — マッチング処理中はタスクが `submitted` 状態になり、オーディエンスの準備が完了するか小さすぎる場合に `completed` になります。

**エージェントワークフロー**: `push_notification_config` を設定してアップロードします。セッション終了前に `audience_id` と `account_id` を外部化します。`status: "ready"` の Webhook が発火したら再開して `create_media_buy` に進む。

## 非同期パターン

二つの異なる非同期パターンがあります——セラーの振る舞いに応じて正しいものを選んでください:

**オーディエンスごとの非同期マッチング**（一般的）: 同期操作自体は同期的に解決され、オーディエンスごとの結果を即座に返します。マッチングがまだ実行中のオーディエンスは、`status: "processing"` とともに同期的な成功レスポンスで返ってきます。バイヤーは、後続のディスカバリー専用呼び出しまたはウェブフックを通じて終端の状態（`ready` / `too_small`）を突き合わせます。これは上記の [オーディエンスステータス](#オーディエンスステータス) 列挙が扱うケースです。

**操作レベルの非同期**（あまり一般的でない）: 同期全体がキューに入れられます——取り込みがバッチ化されている、ガバナンスのレビューがアップロードをゲートしている、またはマッチングを開始する前に上流のクリーンルームのフローが確定しなければならない、といった理由でセラーが応答前にオーディエンスごとの結果を返せない場合です。レスポンスは submitted エンベロープです:

* トップレベルの `status: "submitted"` と `task_id`
* `message` — 任意の人が読める説明
* このエンベロープには `audiences` 配列なし

`tasks/get` をポーリングするか、ウェブフックを待ちます。完了アーティファクトが、項目ごとの `action`/`status` の結果を持つ `audiences` 配列を運びます。操作レベルの失敗は、タスク上の `status: "failed"` として表面化します。

**参照:** ウェブフックの設定については[ウェブフック](/docs/building/by-layer/L3/webhooks)を参照。

## ハッシュ化の要件

送信前にすべての識別子を SHA-256 でハッシュ化すること。まず正規化を行う:

| 識別子     | 正規化           | 例                          |
| ------- | ------------- | -------------------------- |
| メールアドレス | 小文字化、前後の空白を除去 | `alice@example.com` → ハッシュ |
| 電話番号    | E.164 形式      | `+12065551234` → ハッシュ      |
| MAID    | 正規化不要         | そのまま使用                     |

```javascript test=false theme={null}
import { createHash } from "crypto";

const hashEmail = (email) =>
  createHash("sha256").update(email.toLowerCase().trim()).digest("hex");

const hashPhone = (e164Phone) =>
  createHash("sha256").update(e164Phone).digest("hex");
```

## プライバシーに関する考慮事項

スキーマは平文のメールアドレスや電話番号を一切運びません——バイヤーは送信前にハッシュ化しなければなりません（MUST）。セラーは同じアルゴリズムで自社のユーザーデータを独立してハッシュ化することでマッチングを行います。

**ハッシュ化された識別子は仮名化された PII であり、匿名ではありません。** メールアドレスや電話番号のソルトなしの SHA-256 は、メールと E.164 の名前空間の事前計算された辞書を通じて復元可能です。したがって、`hashed_email` と `hashed_phone` は、保持、同意、アクセス制御、データ主体の請求の目的で PII として扱わなければなりません（MUST）。オペレーターのドキュメントや DPA でこれらを「プライバシー保護的」と説明しないでください。[プライバシーに関する考慮事項](/docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous)を参照。

**バイヤーの義務**: バイヤーは管轄区域に関わらず、オーディエンスデータを処理・共有するための適法根拠を持つ責任があります。規制対象市場で活動するセラーに GDPR の適法根拠を伝えるために、各オーディエンスに `consent_basis` を含めること — 一部のセラーは EU オーディエンスに対してこのフィールドを必須としています。

**データ取り扱い**: アップロード後のデータ処理と保持は、セラーとの契約に基づいて管理されます。オーディエンスデータをアップロードする前に、セラーのデータ処理条件を確認すること。

## エラー処理

| エラーコード                | 説明                                                            | 対処方法                                        |
| --------------------- | ------------------------------------------------------------- | ------------------------------------------- |
| `ACCOUNT_NOT_FOUND`   | アカウントが存在しない                                                   | `account_id` を確認する                          |
| `REFERENCE_NOT_FOUND` | 削除対象のオーディエンスが存在しない、またはアクセスできない（`error.field` = `audience_id`） | `audience_id` を確認するか `remove` を省略する         |
| `INVALID_HASH_FORMAT` | 識別子が期待されるハッシュ形式と一致しない                                         | SHA-256 の16進数エンコードを確認する（64 文字、小文字）          |
| `RATE_LIMITED`        | 同期リクエストが多すぎる                                                  | 指数バックオフで再試行します。ポーリングは 15 分以上の間隔で行うこと        |
| `CALL_TOO_LARGE`      | ペイロードのメンバー数が多すぎる                                              | ペイロードはすべてのオーディエンスを合わせて最大 100,000 メンバーに制限される |

## 次のステップ

* [ターゲティング](/docs/media-buy/advanced-topics/targeting) — `targeting_overlay.audience_include` と `audience_exclude` でオーディエンスを参照します
* [create\_media\_buy](/docs/media-buy/task-reference/create_media_buy) — パッケージにオーディエンスターゲティングを適用します
* [コンバージョントラッキング](/docs/media-buy/conversion-tracking/) — オーディエンスターゲティングキャンペーンの成果をトラッキングします
