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 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
Response Schema: /schemas/v3/account/sync-accounts-response.json
Quick start
Sync a single advertiser account and check the resulting status:Request parameters
Account entry fields:
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 anaccounts 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). Noaccountsarray is present.
accounts OR errors, never both.
Per-account fields:
Account status
Async notifications
Whenpush_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.
push_notification_config, poll 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+billingat 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 anaccount_id, but the natural-keyAccountRefremains valid for subsequent calls. - Settings-update mode —
account(an AccountRef) at the entry root, withbrand/operator/billingabsent. 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 vialist_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.
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 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_configsto 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.
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 endpointurl— 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 asaccount.status_changedas a per-account validation failure withINVALID_REQUESTorVALIDATION_ERRORinaccounts[].errors[], anderror.fieldMUST point at the invalidevent_typesentry.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 aspush_notification_config.authenticationapply. Credentials are write-only — sellers omit them on reads.active(defaulttrue) — setfalseto pause a subscriber without removing the registration. Sellers MAY skip only the outbound proof challenge whileactive: 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 asactive: true, the seller MUST validate the URL, apply the SSRF rules in Webhook URL validation, 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 and webhook-challenge-response.json.
2xx with a JSON body containing exactly one echo field:
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:
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 — 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: 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 for capability declaration and event semantics.
Common scenarios
Agency syncing multiple brands
Direct brand purchase
Handling rejection
When a seller declines a request, the account entry hasstatus: "rejected":
Error handling
Next steps
- list_accounts — Poll for status changes on pending accounts
- sync_governance — Sync governance agents to accounts
- Accounts and agents — Billing models, trust models, and authorized operators
- Brand protocol — How seller agents resolve brand identity from the
brand.domain - get_adcp_capabilities — Discover
supported_billingandrequire_operator_authbefore syncing accounts