> ## Documentation Index
> Fetch the complete documentation index at: https://agenticadvertisingorg-feature-feedback.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# sync_accounts

> sync_accounts provisions buyer-declared accounts or updates settings on existing accounts with an AdCP seller agent.

Sync advertiser accounts with a seller for one or more brand/operator pairs, or update settings on existing accounts when the seller exposes that mode. Brands are identified by a `brand` object containing `domain` + optional `brand_id`, resolved via `/.well-known/brand.json`.

`sync_accounts` is used across all seller protocols: media buy agents, signals agents, governance agents, and creative agents. In provisioning mode it declares the buyer's intent — the seller provisions or links accounts internally. Use provisioning mode for buyer-declared accounts (`require_operator_auth: false`) and use natural keys (`brand` + `operator`) on subsequent requests. Sellers MAY echo an `account_id` as an internal handle, but they MUST continue accepting the natural-key `AccountRef` for accounts provisioned this way. For account-id namespaces, discover seller-assigned account IDs via [`list_accounts`](/docs/accounts/tasks/list_accounts) or out-of-band onboarding; `sync_accounts` provisioning for account-id namespaces is out of scope unless a future explicit capability declares that mode. If such a seller exposes `sync_accounts` today, use only settings-update mode keyed by `account_id`.

**Response Time**: \~1s. Account provisioning is synchronous; credit and legal review may require human action (indicated by `status: "pending_approval"` with a `setup.url`).

**Request Schema**: [`/schemas/v3/account/sync-accounts-request.json`](https://adcontextprotocol.org/schemas/v3/account/sync-accounts-request.json)
**Response Schema**: [`/schemas/v3/account/sync-accounts-response.json`](https://adcontextprotocol.org/schemas/v3/account/sync-accounts-response.json)

## Quick start

Sync a single advertiser account and check the resulting status:

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

  const result = await testAgent.syncAccounts({
    accounts: [
      {
        brand: { domain: "acme-corp.com" },
        operator: "acme-corp.com",
        billing: "operator",
      },
    ],
  });

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

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

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

  for (const account of validated.accounts) {
    console.log(`${account.brand.domain}: ${account.status}`);
    if (account.status === "pending_approval" && account.setup?.url) {
      console.log(`  Complete setup at: ${account.setup.url}`);
    }
  }
  ```

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

  async def main():
      result = await test_agent.simple.sync_accounts(
          accounts=[
              {
                  "brand": {"domain": "acme-corp.com"},
                  "operator": "acme-corp.com",
                  "billing": "operator",
              },
          ],
      )

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

      for account in result.accounts:
          print(f"{account.brand['domain']}: {account.status}")
          if account.status == 'pending_approval' and hasattr(account, 'setup') and account.setup:
              print(f"  Complete setup at: {account.setup.url}")

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

## Request parameters

| Parameter                  | Type    | Required | Description                                                                                                                                       |
| -------------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accounts`                 | array   | Yes      | Array of account entries to sync (see below).                                                                                                     |
| `delete_missing`           | boolean | No       | When true, accounts previously synced by this agent but not in this request are deactivated. Scoped to the authenticated agent. Default: `false`. |
| `dry_run`                  | boolean | No       | When true, preview what would change without applying. Default: `false`.                                                                          |
| `push_notification_config` | object  | No       | Webhook for async notifications when account status changes (e.g., `pending_approval` transitions to `active`).                                   |

**Account entry fields:**

| Field                  | Type    | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ---------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `brand`                | object  | Yes      | Brand reference identifying the advertiser. Contains `domain` (house domain where brand.json is hosted) and optional `brand_id` (for multi-brand houses). See [brand-ref](/docs/brand-protocol/brand-json).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `operator`             | string  | Yes      | Domain of the entity operating on the brand's behalf (e.g. `pinnacle-media.com`). When the brand operates directly, set to the brand's domain. Verified against the brand's `authorized_operators` in brand.json.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `billing`              | string  | Yes      | Who should be invoiced: `operator`, `agent`, or `advertiser`. Check `get_adcp_capabilities` for `supported_billing` to see what the seller accepts at the capability level. The seller must either accept this billing model or reject the request. Sellers MAY additionally reject a value the seller-wide capability accepts when the calling buyer agent's commercial relationship does not permit it — e.g., a buyer agent onboarded as passthrough-only (no payments relationship — only the operator can be invoiced). The two gates use distinct error codes — `BILLING_NOT_SUPPORTED` for the seller-wide capability gate, `BILLING_NOT_PERMITTED_FOR_AGENT` for the per-buyer-agent gate — so agents can dispatch on autonomous-retry vs human-onboarding without parsing prose. See [Buyer-agent identity](/docs/building/integration/accounts-and-agents#buyer-agent-identity) and [Billing and Account Setup](/docs/building/implementation/error-handling#billing-and-account-setup). |
| `billing_entity`       | object  | No       | Structured business entity details for the party responsible for payment. Contains `legal_name` (required), plus optional `vat_id`, `tax_id`, `registration_number`, `address`, `contacts`, and `bank`. Bank details are write-only — included in requests but never echoed in responses. See [billing entity and invoice recipient](/docs/building/integration/accounts-and-agents#billing-entity-and-invoice-recipient).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `payment_terms`        | string  | No       | Payment terms for this account: `net_15`, `net_30`, `net_45`, `net_60`, `net_90`, or `prepay`. The seller must either accept these terms or reject the account — terms are never silently remapped. When omitted, the seller applies its default terms.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `sandbox`              | boolean | No       | When true, set up a sandbox account with no real platform calls or billing. Only applicable to buyer-declared accounts (`require_operator_auth: false`). For account-id namespaces, sandbox accounts are pre-existing test accounts discovered via `list_accounts` or supplied out-of-band.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `notification_configs` | array   | No       | Account-level webhook subscribers for events that outlive any single media buy: creative lifecycle notifications and wholesale feed change webhooks. Omit to leave existing subscribers unchanged; send `[]` to remove all subscribers; send a full array to replace. Entries are keyed by account-scoped `subscriber_id`; an existing `subscriber_id` is upserted, and persisted IDs absent from the sent array are removed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

**Natural key**: The tuple `(brand, operator, sandbox)` uniquely identifies an account relationship. `{brand: {domain: "acme-corp.com"}, operator: "acme-corp.com"}` (direct) is a different account from `{brand: {domain: "acme-corp.com"}, operator: "pinnacle-media.com"}` (via agency). Adding `sandbox: true` provisions a sandbox account for the same brand/operator pair — no real platform calls or billing.

## Response

**Success response:**

Returns an `accounts` array with per-account results. Individual accounts may be pending, rejected, or failed even when the operation succeeds.

**Error response:**

* `errors` -- Array of operation-level errors (auth failure, service unavailable). No `accounts` array is present.

**Note:** Responses use discriminated unions -- you get either `accounts` OR `errors`, never both.

**Per-account fields:**

| Field                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `brand`                | Echoed from request. Object with `domain` and optional `brand_id`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `operator`             | Echoed from request.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `name`                 | Seller's display name for the account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `action`               | What happened: `created`, `updated`, `unchanged`, or `failed`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `status`               | Current state of the account (see [Account status](#account-status)).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `billing`              | Billing model applied. Matches the requested value.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `billing_entity`       | Business entity details for the invoiced party, echoed from the request. Sellers may add fields the agent omitted (e.g., `registration_number` from a credit check) but must not return data from a different entity. Bank details are omitted (write-only).                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `account_scope`        | How the seller scoped this account: `operator` (shared across brands for this operator), `brand` (shared across operators for this brand), `operator_brand` (dedicated to this operator+brand pair), or `agent` (agent-scoped account shared across declared brand/operator pairs). See [account scope](/docs/building/integration/accounts-and-agents#account-scope).                                                                                                                                                                                                                                                                                                                                                            |
| `setup`                | Present when `status: "pending_approval"`. Contains `url` for completing credit or legal setup, `message` explaining what's needed, and optional `expires_at`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `rate_card`            | Seller-assigned rate card identifier (when applicable).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `payment_terms`        | Payment terms agreed for this account: `net_15`, `net_30`, `net_45`, `net_60`, `net_90`, or `prepay`. When the account is active, these are the binding terms for all invoices.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `credit_limit`         | Maximum outstanding balance as `{amount, currency}`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `errors`               | Per-account errors (only present when `action: "failed"`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `warnings`             | Non-fatal notices.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `sandbox`              | Whether this is a sandbox account, echoed from the request. Only present for buyer-declared accounts.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `notification_configs` | Optional. Current persisted account-level webhook subscribers after applying the request. Present on `created`, `updated`, and `unchanged` results when the request included `notification_configs` or the account already has persisted subscribers. Each entry carries `subscriber_id`, `url`, `event_types[]`, and `active`; `authentication.credentials` is omitted (write-only).                                                                                                                                                                                                                                                                                                                                             |
| `authorization`        | Optional. The calling agent's scope grant for this account — `allowed_tasks`, `field_scopes`, `scope_name`, `read_only`. Applies to every vendor agent type (media-buy, signals, governance, creative, brand) — the Accounts Protocol surface is shared. Present on `created`, `updated`, and `unchanged` results; omitted on `failed` results. Vendor agents that support scope introspection SHOULD populate this; media-buy sales agents claiming the `attestation_verifier` standard scope MUST populate it. Absence means the vendor agent does not advertise introspectable scope; callers MUST NOT infer access from absence. See [Caller authorization](/docs/accounts/overview#caller-authorization) for the full shape. |

### Account status

| Status             | Meaning                                | Next step                                                                                                         |
| ------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `active`           | Ready to use                           | Use [account reference](/docs/building/integration/accounts-and-agents#account-references) in protocol operations |
| `pending_approval` | Seller reviewing                       | Human may need to visit `setup.url` to complete credit or legal process. Poll `list_accounts` for updates.        |
| `rejected`         | Seller declined the request            | Review rejection reason in `warnings`, adjust and retry, or contact seller                                        |
| `payment_required` | Credit limit reached or funds depleted | Add funds or increase credit limit. Route spend to other accounts.                                                |
| `suspended`        | Was active, now paused                 | Contact seller to resolve                                                                                         |
| `closed`           | Was active, now terminated             | --                                                                                                                |

### Async notifications

When `push_notification_config` is provided and the seller returns `pending_approval`, the seller sends a webhook notification when the account status changes (e.g., approved → `active`, declined → `rejected`).

For provisioning requests, the notification payload includes the `(brand, operator)` natural key so the buyer can correlate it to the original sync request. When the seller also returns a seller-assigned `account_id`, the notification includes it as a convenience handle; buyers still follow the seller's declared account-reference model for subsequent calls.

```json theme={null}
{
  "brand": { "domain": "nova-brands.com", "brand_id": "glow" },
  "operator": "pinnacle-media.com",
  "status": "active",
  "account_id": "acc_glow_001"
}
```

If the buyer did not provide `push_notification_config`, poll [`list_accounts`](/docs/accounts/tasks/list_accounts) to check for status changes.

## Two modes: provisioning vs. settings-update

Each per-account entry uses one of two key shapes, never both:

* **Provisioning mode** — flat `brand` + `operator` + `billing` at the entry root. The seller provisions or upserts accounts. Used for buyer-declared accounts (`require_operator_auth: false`). This is the shape AdCP 3.0 shipped with. Sellers MAY echo an `account_id`, but the natural-key `AccountRef` remains valid for subsequent calls.
* **Settings-update mode** — `account` (an [AccountRef](/docs/building/integration/accounts-and-agents#account-references)) at the entry root, with `brand`/`operator`/`billing` absent. The seller updates the account's settable state — no provisioning side effects. Used for account-id namespaces only when the seller exposes settings updates through this task; upstream-managed accounts are discovered via `list_accounts`, while seller-defined account IDs may be supplied out-of-band. Buyer-declared account sellers MAY also accept this mode for settings updates against accounts they previously provisioned.

Schema enforces the exclusivity via `oneOf` — sending both shapes on the same entry is a validation error. Sellers that don't implement settings-update mode reject `account`-keyed entries with `UNSUPPORTED_PROVISIONING`; sellers that don't provision through `sync_accounts`, including account-id namespaces, reject natural-key provisioning entries with the same code.

## Account-level webhook subscriptions

`notification_configs[]` carries account-level webhook subscribers for notifications whose lifecycle outlives any single media buy — `creative.status_changed`, `creative.purged`, wholesale feed change webhooks (`product.*`, `signal.*`, `wholesale_feed.bulk_change`), and future account-anchored resource events after those event types are added to `notification-type.json`.

This is not the account object's lifecycle event stream. There are no `account.created`, `account.updated`, `account.status_changed`, or `account.closed` notification types today. Account status changes are observed by polling [`list_accounts`](/docs/accounts/tasks/list_accounts) or, for the async result of a `sync_accounts` provisioning request, through `push_notification_config` on this task.

For these event types, "wholesale feed" means the seller's buyable wholesale product and signals feeds returned by `get_products` or `get_signals`; it is not the buyer-provided feeds managed by `sync_catalogs`.

Permitted in **both** provisioning and settings-update modes. Declarative semantics:

* Omit `notification_configs` to leave the account's existing subscribers unchanged.
* Send `notification_configs: []` to remove every subscriber on that account.
* Send a non-empty array to replace the account's current set with the submitted set.

Within one account, `subscriber_id` is the stable logical key. Re-sending an existing `(account_id, subscriber_id)` with a different `url`, `event_types`, `authentication`, or `active` value replaces that subscriber's active config rather than creating a duplicate. The seller MUST NOT merge the submitted array with persisted state: persisted subscribers whose `subscriber_id` does not appear in the sent array are removed. Paused entries (`active: false`) are subject to the same replacement semantics; to preserve a paused subscription, re-include it with `active: false` in the sent array. Duplicate `subscriber_id` values within the same submitted array are invalid. The replacement is account-scoped; the same `subscriber_id` MAY be reused on a different account.

If any entry in the submitted replacement set fails validation or activation proof, the seller rejects that account entry with `action: "failed"` and leaves the account's previous `notification_configs[]` set unchanged. Sellers MUST NOT partially apply a replacement set and silently drop only the failed subscriber.

Each entry has:

* `subscriber_id` — buyer-supplied identifier, unique within the account; echoed on every fire so multi-subscriber accounts can route by endpoint
* `url` — HTTPS endpoint URL. Sellers MUST complete an endpoint activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.
* `event_types[]` — types the subscriber wants. Only account-anchored types are permitted (today: `creative.status_changed`, `creative.purged`, `product.created`, `product.updated`, `product.priced`, `product.removed`, `signal.created`, `signal.updated`, `signal.priced`, `signal.removed`, `wholesale_feed.bulk_change`). Sellers MUST reject any media-buy-anchored type (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and any undefined account-lifecycle name such as `account.status_changed` as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR` in `accounts[].errors[]`, and `error.field` MUST point at the invalid `event_types` entry.
* `authentication` (optional) — legacy Bearer or HMAC-SHA256. Omit to use the default RFC 9421 webhook profile. When present, the same signed-registration downgrade-resistance rules as `push_notification_config.authentication` apply. Credentials are write-only — sellers omit them on reads.
* `active` (default `true`) — set `false` to pause a subscriber without removing the registration. Sellers MAY skip only the outbound proof challenge while `active: false`; they MUST still enforce HTTPS parsing, hostname normalization, and reserved-range rejection on write. Paused subscribers MUST NOT receive fires until reactivated. Reactivation MUST repeat full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof.

### Endpoint proof of control

Before persisting or echoing an entry as `active: true`, the seller MUST validate the URL, apply the SSRF rules in [Webhook URL validation](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), and prove that the receiver controls the endpoint.

Proof is required when there is no current valid proof for the tuple `(account_id, subscriber_id, normalized url, authentication mode/credential binding, normalized event_types)`. Changing the subscriber ID, normalized URL, authentication mode/credential binding, or `event_types[]` requires fresh proof before the new set can become active. The challenge POST itself MUST be signed with the seller's RFC 9421 webhook profile key even when the candidate config selects legacy delivery auth. New signers use `adcp_use: "request-signing"`; deprecated `webhook-signing` keys remain accepted during the compatibility window. The receiver MUST verify the RFC 9421 signature and MUST reject the challenge unless `seller_agent_url`, `delivery_auth`, and `event_types` match the pending registration. Sellers MAY re-challenge on their own proof-expiration policy.

The standard challenge is an HTTPS POST to the candidate `url` with a JSON body containing `type`, `challenge`, `account_id`, `subscriber_id`, `seller_agent_url`, `delivery_auth`, and `event_types`. The canonical schemas are [`webhook-challenge.json`](https://adcontextprotocol.org/schemas/v3/core/webhook-challenge.json) and [`webhook-challenge-response.json`](https://adcontextprotocol.org/schemas/v3/core/webhook-challenge-response.json).

```json theme={null}
{
  "type": "webhook.challenge",
  "challenge": "example-challenge-token-000000000000",
  "account_id": "acct_123",
  "subscriber_id": "buyer-primary",
  "seller_agent_url": "https://seller.example/adcp",
  "delivery_auth": { "mode": "rfc9421" },
  "event_types": ["creative.status_changed"]
}
```

The receiver proves control by returning HTTP `2xx` with a JSON body containing exactly one echo field:

```json theme={null}
{ "challenge": "example-challenge-token-000000000000" }
```

Sellers MUST also accept the backward-compatible alias:

```json theme={null}
{ "token": "example-challenge-token-000000000000" }
```

The challenge value MUST be cryptographically random, single-use, and scoped to the registration tuple. A failed, non-`2xx`, malformed, mismatched, or timed-out challenge means proof failed. Sellers SHOULD use the same outbound fetch caps as SSRF validation (10 second connect and 10 second read) and SHOULD make at most one initial challenge POST on the `sync_accounts` critical path. A seller MAY retry one transient network failure before returning if it uses the same challenge value and still completes within its request budget; otherwise the buyer retries by re-sending `sync_accounts`.

On proof failure, the seller returns a per-account failure with `action: "failed"`, `errors[].code: "VALIDATION_ERROR"` (or `INVALID_REQUEST` for malformed URLs), and `error.field` pointing at `accounts[i].notification_configs[j].url`. The previous persisted subscriber set remains unchanged. `dry_run: true` MUST NOT send network challenges; it can only report structural validation and what would require proof.

Example — register a buyer-side endpoint plus an audit bus on an account-id namespace account:

```json theme={null}
{
  "idempotency_key": "f2c4b7d9-6789-49bc-defa-2345678901bc",
  "accounts": [
    {
      "account": { "account_id": "acc_acme_pinnacle" },
      "notification_configs": [
        {
          "subscriber_id": "buyer-primary",
          "url": "https://buyer.example/webhooks/adcp/creative",
          "event_types": ["creative.status_changed", "creative.purged"],
          "active": true
        },
        {
          "subscriber_id": "audit-bus",
          "url": "https://audit.buyer.example/adcp/ingest",
          "event_types": ["creative.status_changed", "creative.purged"],
          "active": true
        }
      ]
    }
  ]
}
```

Example — register a wholesale feed mirror subscriber for wholesale product and signal changes:

```json theme={null}
{
  "idempotency_key": "a8af8cf1-89bd-41f3-b27d-7ee7e9f8d2e4",
  "accounts": [
    {
      "account": { "account_id": "acc_acme_pinnacle" },
      "notification_configs": [
        {
          "subscriber_id": "wholesale-feed-sync",
          "url": "https://buyer.example/webhooks/adcp/wholesale-feed",
          "event_types": [
            "product.created",
            "product.updated",
            "product.priced",
            "product.removed",
            "signal.created",
            "signal.updated",
            "signal.priced",
            "signal.removed",
            "wholesale_feed.bulk_change"
          ],
          "active": true
        }
      ]
    }
  ]
}
```

Governance agents registered via [`sync_governance`](/docs/accounts/tasks/sync_governance) are **not** implicitly subscribed to these webhooks. If your governance agent should also receive creative-lifecycle fires, register its URL as a separate `notification_configs[]` entry — explicit, auditable, with its own `event_types[]` filter.

Verify applied state via [`list_accounts`](/docs/accounts/tasks/list_accounts) — the response carries the current persisted `notification_configs[]` per account with credentials redacted. `sync_accounts` also echoes the current sanitized set on `created`, `updated`, and `unchanged` results when the request included `notification_configs` or any persisted subscribers already exist.

Wholesale feed notifications are registered here, not through a separate subscription task. The webhook body is [`wholesale-feed-webhook.json`](https://adcontextprotocol.org/schemas/v3/core/wholesale-feed-webhook.json): it carries the changed product, signal, or bulk-change summary plus the post-change `wholesale_feed_version`. Sellers MUST apply the same per-subscriber authorization and scope predicate used by the corresponding wholesale read before emitting each webhook. Receivers MAY apply the payload to local mirrors; use `get_products` / `get_signals` with `if_wholesale_feed_version` to repair missed or distrusted pushes and before binding spend or authority. See [wholesale\_feed\_webhooks](/docs/protocol/get_adcp_capabilities#wholesale_feed_webhooks) for capability declaration and event semantics.

## Common scenarios

### Agency syncing multiple brands

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

  const result = await testAgent.syncAccounts({
    accounts: [
      {
        brand: { domain: "nova-brands.com", brand_id: "spark" },
        operator: "pinnacle-media.com",
        billing: "operator",
      },
      {
        brand: { domain: "nova-brands.com", brand_id: "glow" },
        operator: "pinnacle-media.com",
        billing: "operator",
      },
    ],
  });

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

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

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

  for (const account of validated.accounts) {
    if (account.status === "active") {
      console.log(`Ready: ${account.brand.domain}/${account.brand.brand_id} → ${account.status}`);
    } else if (account.status === "pending_approval") {
      console.log(`Setup required for ${account.brand.brand_id}: ${account.setup?.url}`);
      // Poll list_accounts until status becomes active
    }
  }
  ```

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

  async def main():
      result = await test_agent.simple.sync_accounts(
          accounts=[
              {
                  "brand": {"domain": "nova-brands.com", "brand_id": "spark"},
                  "operator": "pinnacle-media.com",
                  "billing": "operator",
              },
              {
                  "brand": {"domain": "nova-brands.com", "brand_id": "glow"},
                  "operator": "pinnacle-media.com",
                  "billing": "operator",
              },
          ],
      )

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

      for account in result.accounts:
          if account.status == 'active':
              print(f"Ready: {account.brand['domain']}/{account.brand.get('brand_id')} → {account.status}")
          elif account.status == 'pending_approval':
              print(f"Setup required for {account.brand.get('brand_id')}: {account.setup.url}")
              # Poll list_accounts until status becomes active

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

### Direct brand purchase

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

  const result = await testAgent.syncAccounts({
    accounts: [
      {
        brand: { domain: "acme-corp.com" },
        operator: "acme-corp.com",
        billing: "operator",
      },
    ],
  });

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

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

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

  const account = validated.accounts[0];
  if (account.status === "active") {
    console.log(`Ready: ${account.brand.domain} — ${account.status}`);
  } else if (account.status === "pending_approval") {
    console.log(`Setup required: ${account.setup?.url}`);
    // Poll list_accounts until status becomes active
  }
  ```

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

  async def main():
      result = await test_agent.simple.sync_accounts(
          accounts=[
              {
                  "brand": {"domain": "acme-corp.com"},
                  "operator": "acme-corp.com",
                  "billing": "operator",
              },
          ],
      )

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

      account = result.accounts[0]
      if account.status == 'active':
          print(f"Ready: {account.brand['domain']} — {account.status}")
      elif account.status == 'pending_approval':
          print(f"Setup required: {account.setup.url}")
          # Poll list_accounts until status becomes active

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

### Handling rejection

When a seller declines a request, the account entry has `status: "rejected"`:

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

  const result = await testAgent.syncAccounts({
    accounts: [
      {
        brand: { domain: "acme-corp.com", brand_id: "clearance" },
        operator: "acme-corp.com",
      },
    ],
  });

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

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

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

  for (const account of validated.accounts) {
    if (account.status === "rejected") {
      console.log("Account request was rejected");
      if (account.warnings?.length) {
        console.log(`Reason: ${account.warnings.join(", ")}`);
      }
    }
  }
  ```

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

  async def main():
      result = await test_agent.simple.sync_accounts(
          accounts=[
              {
                  "brand": {"domain": "acme-corp.com", "brand_id": "clearance"},
                  "operator": "acme-corp.com",
              },
          ],
      )

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

      for account in result.accounts:
          if account.status == 'rejected':
              print("Account request was rejected")
              warnings = getattr(account, 'warnings', None)
              if warnings:
                  print(f"Reason: {', '.join(warnings)}")

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

## Error handling

| Error Code                        | Description                                                                                                                                                                                                                                                   | Resolution                                                                                                                                                                  |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACCOUNT_NOT_FOUND`               | Referenced account does not exist or is not accessible                                                                                                                                                                                                        | Check `account_id` or re-sync                                                                                                                                               |
| `BILLING_NOT_SUPPORTED`           | Seller-wide capability gate (`supported_billing` does not include the value) or per-account-relationship gate; see [Billing and Account Setup](/docs/building/implementation/error-handling#billing-and-account-setup)                                        | Check `get_adcp_capabilities` for `supported_billing`, adjust or omit `billing`; inspect `error.details.scope` to disambiguate capability vs account scope                  |
| `BILLING_NOT_PERMITTED_FOR_AGENT` | Seller-wide capability accepts the value, but the calling buyer agent's commercial relationship does not (e.g., passthrough-only — no payments relationship); see [Buyer-agent identity](/docs/building/integration/accounts-and-agents#buyer-agent-identity) | Retry with `error.details.suggested_billing` (typically `operator`) when present; when absent, surface to a human — the agent cannot extend its own commercial relationship |
| `PAYMENT_TERMS_NOT_SUPPORTED`     | Seller does not accept the requested payment terms                                                                                                                                                                                                            | Omit `payment_terms` to accept the seller's default, or negotiate offline                                                                                                   |
| `ACCOUNT_PAYMENT_REQUIRED`        | Account has an outstanding balance requiring payment                                                                                                                                                                                                          | Resolve outstanding balance or route to another account                                                                                                                     |
| `ACCOUNT_SUSPENDED`               | Account is suspended                                                                                                                                                                                                                                          | Contact seller to resolve                                                                                                                                                   |
| `BRAND_REQUIRED`                  | Billable operation attempted without brand reference                                                                                                                                                                                                          | Include `brand` in the request                                                                                                                                              |

## Next steps

* [list\_accounts](/docs/accounts/tasks/list_accounts) -- Poll for status changes on pending accounts
* [sync\_governance](/docs/accounts/tasks/sync_governance) -- Sync governance agents to accounts
* [Accounts and agents](/docs/building/integration/accounts-and-agents) -- Billing models, trust models, and authorized operators
* [Brand protocol](/docs/brand-protocol/brand-json) -- How seller agents resolve brand identity from the `brand.domain`
* [get\_adcp\_capabilities](/docs/protocol/get_adcp_capabilities) -- Discover `supported_billing` and `require_operator_auth` before syncing accounts
