sync_principal replaces the principal’s standing configuration with one AdCP seller agent: webhooks, reusable reporting destinations, and its position in capability negotiation. The three tiers of the hierarchy:
get_adcp_capabilities states what the seller can support; sync_principal states which of it this principal uses; account-level tasks authorize work for particular advertisers.
The task does not register a buyer agent and does not accept a buyer-agent URL or identity in its body. Who the principal is comes only from authenticated transport.
Request schema: /schemas/3.2.0-beta.10/protocol/sync-principal-request.json
Response schema: /schemas/3.2.0-beta.10/protocol/sync-principal-response.json
Identity and authority
The principal is a party, in the same sense the protocol already bills and authorizes parties. It comes in two kinds:- A buyer agent authenticating as itself — its registered workload, OAuth client-credentials subject, server-side API-key owner, verified signed-request identity, or equivalent machine principal. The operators an agent acts for are agent-scoped labels within its own principal space, asserted the same way
operator_identityis on accounts; they confer no identity across agents. - An operator-side identity — a person at the operator authenticated as themselves. The client application they act through (Claude, ChatGPT, the seller’s UI) is the instrument, never the owner. A person using ChatGPT therefore configures under their own login at the seller; nothing about ChatGPT is registered.
brand.json agents[] entry—but only after the seller verifies the publication chain and confirms that entry authorizes the request-signing key. The URL is therefore a verified identity input, not a body assertion. The operator domain, raw URL spelling, brand_json_url, jwks_uri, kid, key thumbprint, and key generation remain trust or credential-binding facts beneath that identity. Two canonical Agent URLs are two principals even when they share a domain, operator, type, or JWKS endpoint; key rotation under one canonical Agent URL preserves that principal. If the verified chain cannot select exactly one canonical Agent URL, the seller fails closed without reading or creating principal state.
Every applied, current, or recognized readback carries the seller-resolved principal_kind — buyer_agent or operator — resolved solely from the authorization system, never from request content, so per-party policy such as billing gates can rely on it before configuration begins. A request may carry expected_principal_kind as an assertion fence (“I believe I am authenticating as the agent”); on mismatch with the resolved kind the seller rejects with CONFLICT before mutation. The assertion never influences resolution.
The response’s principal_id is a seller-issued audit and routing handle for the principal record. It is not a credential and does not authorize any advertiser account.
Establishing a connection
The principal layer begins after authentication. AdCP defines no credential-registration protocol: the seller’s authorization system — OAuth, API keys, signed requests, workload identity — issues and maps credentials before the first call. The same lifecycle then works for an autonomous buyer agent, a person in an interactive client, an API-key integration, or M2M OAuth:- Authenticate. Autonomous software presents its workload identity; a person presents their delegated user identity. No
register_buyer_agentstep exists or is needed. - Read
get_adcp_capabilities. Discoveradcp.principal, its supported sections, event and delivery offerings, and limits. - Verify the resolved identity. Call
get_principal. Arecognizedresult returns the existing durableprincipal_idand seller-resolvedprincipal_kindwithout standing configuration.unconfiguredmeans stable authentication succeeded but no durable record exists. The read creates neither records nor identifiers. - Call
sync_principal. Declare consumption facts (declarations), register webhook subscribers (includingprincipal.changedand, with explicit scope, account-anchored events), and register reusable reporting destinations. - Authorize advertiser accounts separately through
sync_accountsand account-level tasks. Nothing in steps 1–4 grants account authority. - Bind each account’s feeds to the reusable
destination_refs. - Observe readiness.
get_principalshows destination and declaration state;principal.changedkeeps it fresh.
Discovery
Only call this task whenget_adcp_capabilities includes both:
reporting_destination_offerings is the seller’s objective delivery offering: a buyer selects a compatible pattern, transport, format, access mode, and verification-profile intersection from it before submitting, instead of discovering support through rejections. A destination outside the offering fails the whole request with UNSUPPORTED_FEATURE; a destination inside the offering that fails provider-side setup is reported per destination as validating, action_required, or rejected inside an applied response.
Synchronization semantics
configuration contains one or more independently replaceable sections:
For each submitted section:
- The array is complete desired state for that section.
- Omitting a previously present entry removes it; for reporting destinations, removal is revocation.
[]clears the whole section.- An omitted section remains unchanged.
- All submitted sections apply atomically, or none do. Sections are validated and authorized in full before any proof challenge or grant is issued, so a failed request leaves no external side effects.
dry_run: truereturnsresult.kind: validatedwith the would-be action, without persistence, grants, endpoint challenges, principal IDs, destination references, or configuration versions. Dry-run outcomes and warnings are computed solely against the caller’s own state and never reveal what other principals have registered.
expected_configuration_version is present, the seller applies a new operation only if it equals the current opaque version. A stale version returns CONFLICT without mutation. This prevents two services using the same principal from silently overwriting each other. Read the current version with get_principal before a guarded replacement — never write blind to discover it. Retries of one logical request reuse both its idempotency_key and body. The seller resolves an exact idempotent replay before evaluating the version fence, so a lost successful response remains replayable after the persisted version advances. Idempotency records are scoped to the stable principal; a key first seen under a different principal starts a new operation, never a replay.
configuration_version advances on every successful mutation of the shared underlying state — through this task or through sync_agent_notification_configs — and never on seller-driven setup-state transitions such as a destination becoming ready.
principal-level webhooks
One subscriber registered here can carry every webhook the relationship needs — the “sign me up for everything, once” surface:- Caller-anchored events (
capabilities.changed,principal.changed) fire principal-wide.principal.changedis the invalidation doorbell for seller-driven state transitions — a destination reachingready, a setup action expiring, the accepted declarations intersection changing — and receivers repair by re-readingget_principal. - Account-anchored events (
creative.status_changed,account.change_recorded,product.*, …) may be listed on caller-level subscribers, and require the explicitall_authorized_accounts: trueacknowledgment — subscribing an endpoint to every account is never implicit. Authorization is evaluated at each delivery attempt, including retries: losing authority for an account both stops new fires and suppresses queued retries carrying that account’s data, without touching the subscription. Registration itself requires no account authority and grants none. - Media-buy-anchored cadence reports (
scheduled,final,delayed,adjusted,window_update) stay on each buy’sreporting_webhook— they carry per-buy cadence choices this layer deliberately does not own.impairmentis also media-buy-anchored but fires againstpush_notification_config— it is a dependency-state change on the buy, not a cadence report.
event_types and may set include_future_event_types: true — which extends only to future caller-eligible types classified invalidation-only (identifiers plus a repair pointer, no domain data). Payload-bearing types always require explicit enumeration, so the flag can never silently opt a caller into more-sensitive payloads. Sellers advertise which types they can emit here in principal.caller_event_types; an out-of-offering type fails the request with UNSUPPORTED_FEATURE.
Caller-level and account-level subscriptions to the same event are independent: both fire, and receivers dedupe by the event’s logical notification_id. Setting a subscriber’s active: false is the webhook kill switch — all fires to that endpoint, across every account, halt within the advertised suspension_interval_seconds.
Declarations
Synchronous calls negotiate versions per request, but a webhook fired weeks after registration has no request context to negotiate against. Thedeclarations section is that channel: the caller declares bounded facts about what it can consume, and the seller returns the accepted intersection it will honor for asynchronous interactions.
- Declarations are facts about consumption ability — never preferences or policy.
get_adcp_capabilitiesstays objective seller facts; the intersection is the negotiated contract. - Unknown
experimental_featuresidentifiers are accepted and simply excluded from the intersection, so one declaration works across sellers with different surfaces. - An empty
webhook_signing_algorithmsintersection while any webhook subscriber is active fails the request withUNSUPPORTED_FEATURE— fires would be unverifiable. - A later seller-side change to the accepted intersection fires
principal.changedwith reasondeclarations_intersection_changedand does not advanceconfiguration_version. - The readback persists the full negotiation record: the declared set, the accepted intersection, the
selected_async_adcp_versionthe seller will actually use for pushes, and anexclusionsentry with a reason for every declared value not accepted — so “what can this buyer implementation consume, and what did the seller decide?” always has one durable answer. - Submitting
{}clears the declared set.
Reusable reporting destinations
A destination is registered once on the caller’s principal record and can later be referenced from several advertiser accounts for which that caller is independently authorized. In the example below, the authenticated principal might be a delegated human user in Claude or an autonomous buyer-agent workload; the request body is identical because identity never comes from the payload.destination_ref and one of these states:
destination_ref is not a bearer capability. It identifies one immutable destination generation for the same authenticated principal. Exact replays preserve the reference; changing provider, transport, normalized coordinates or recipient, accepted formats, access mode, or accepted verification profiles requires fresh proof and produces a new reference. Superseded references stay resolvable for existing account bindings and retained reporting history, and remain enumerable to their owner through prior_destination_refs on get_principal. Every use still checks account authorization separately, and unknown, cross-principal, and unauthorized references fail indistinguishably.
Destination lifecycle
The typical flow for a destination that needs provider-native setup:- Register. The caller syncs the destination. The seller validates it against its advertised offering, issues
destination_ref, and returnsstate: "action_required"with a typedsetupobject — for example{"action": "grant_access"}for a BigQuery dataset the seller’s published principal cannot yet reach, or{"action": "accept_share"}for a dataset share awaiting recipient activation. - Complete provider-native setup. The caller performs the action in the provider’s control plane: grants the seller’s principal on the bucket or dataset, accepts the share as the named recipient, or writes the seller-named proof object. Credentials never transit AdCP.
- Poll the read task. The caller polls
get_principaluntil the destination reachesready. Reading never mutates state or the version. Resubmitting the identical section under a freshidempotency_keyis also safe — it returnsaction: "unchanged"and preservesdestination_ref— but the read task is the intended poll mechanism. - Bind. Account-scoped reporting configuration references the
readydestination bydestination_ref, subject to its own authorization.
ready asserts is pattern-specific:
file_transfer— the caller proved control of the normalized location through a seller-verifiable challenge, and the seller successfully wrote and read back a probe object under the prefix. A grant that exists but was never exercised is notready.warehouse_materialization— the seller verified, through access it was actually granted, that it can create and commit into the locator.dataset_share— the named recipient affirmatively completed the provider’s acceptance or activation step and the provider reports the recipient able to read the share. Access modes that grant to a bare identifier with no acceptance step require an equivalent recipient-initiated proof, such as a recipient read of a seller-published proof dataset. Grant creation alone never producesready, and because provider grant propagation can lag, account-level delivery readiness additionally requires consumer-read verification as defined by managed reporting delivery.
Suspension and revocation
Destination deactivation is a delivery control, not just a registration flag:- Suspend — resubmit the destination with
active: false. The seller stops initiating new deliveries to every generation of thatdestination_idwithin its advertisedsuspension_interval_secondsand marks affected account bindings with a typeddestination_suspendedissue. Resubmitting withactive: truelifts the suspension without new proof when nothing else changed. - Revoke — omit the destination from the submitted
reporting_destinationsarray (or submit[]to revoke all). Revocation has every suspension effect, removes the destination from current state, and retains its generations underretired_destinationsfor audit and reporting history. Reusing a retireddestination_idrequires fresh registration and proof and produces a new generation.
Account consolidation
A consolidated destination can receive reporting from many seller accounts. The destination registration proves where this caller may receive data; it does not prove which data the caller may receive. The managed reporting delivery specification (RFC #6911) supplies that second authorization by binding an account-scoped feed configuration to the reusabledestination_ref. Consolidated files, rows, and partitions must retain seller and account identity so downstream consumers can reconcile them independently.
An agent principal serving many operators may set the optional operator_id label on each destination — its own bookkeeping about which operator a destination serves. That enables per-operator isolation (“this operator’s data goes only to their bucket”), audit (“list every destination serving this operator”), and one-call per-operator suspension or revocation when an operator offboards. The label confers no identity: matching operator_id values or domains across different principals are never linked, and each agent maps its operators its own way.
Verification profiles
Destination setup advertises verification profiles the caller can accept:native_commit: provider-native transaction/version evidence with row counts and control totals. Appropriate for frequent warehouse snapshots when allowed by the feed profile.manifest_checksums: a manifest-last file commit with cryptographic object checksums.canonical_digest: recomputation of a canonical logical-content digest. Appropriate for billing and strict-conformance feeds.
Secrets and proof
AdCP carries destination coordinates and recipient identifiers, never provider credentials. Private keys, passwords, bearer profiles, signed URLs, and cloud credentials are exchanged through provider-native control planes or an out-of-band onboarding channel. The schema pattern onlocation is a transport guard, not the enforcement boundary: sellers additionally screen canonicalized values for credential shapes the pattern cannot express, such as userinfo-style key:secret@host prefixes inside an otherwise valid locator.
Before proof binding and uniqueness comparison, sellers canonicalize coordinates per transport — Unicode NFC, lowercase scheme and host authority, no default ports, canonical path separators — and reject locators or recipient identities whose host authority is not ASCII after canonicalization. Proof binds to the canonical form, so cosmetic variations of the same coordinates cannot evade the fresh-proof requirement.
Before a destination becomes ready, the seller completes the pattern-specific proof and binds it to the authenticated principal, seller agent, destination ID, normalized coordinates, and accepted delivery contract. A changed proof-bound tuple requires fresh proof and a new immutable destination reference; replacement never mutates the meaning of a reference already used by an account configuration.
For notification_configs, the endpoint proof and webhook-signing rules are identical to sync_agent_notification_configs. Implementations exposing both tasks maintain one underlying caller-scoped subscriber set.
Errors
Failed responses carryresult.kind: "failed" with structured errors and deliberately cannot carry a principal ID, version, or configuration state.
Provider-side setup failures for in-offering destinations are not request errors: they surface as
validating, action_required, or rejected destination states inside an applied response.
Compatibility
sync_agent_notification_configs remains the specialized compatibility task for agents that only support capability-change subscribers. When capability_changes.notifications.registration_task is sync_principal, clients manage those subscribers through the notification_configs section here.
The broader task does not turn get_adcp_capabilities into caller-specific state. Capabilities remain cacheable seller facts; principal configuration remains authenticated caller state.