> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# sync_principal

> sync_principal synchronizes authenticated caller-scoped webhooks and reusable reporting destinations with an AdCP agent.

A **principal** is the durable party behind an authenticated caller — a buyer agent, or an operator-side identity. `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:

| Tier | Entity | Surface |
| - | - | - |
| 1 | **Principal** — who you are, resolved from authenticated transport | `sync_principal` / [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal) |
| 2 | The principal's standing configuration with this seller | carried on the principal record |
| 3 | **Accounts** — principal × advertiser authority, feeds, schedules | [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) and domain tasks |

[`get_adcp_capabilities`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/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.

Three consequences that are easy to get wrong:

* **It is optional.** The principal exists as soon as a stable credential authenticates. A buyer that never calls `sync_principal` can still use `sync_accounts`, [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts), and every domain task; it only forgoes principal-wide webhooks, reusable reporting destinations, and declared async versions.
* **There is one principal per credential, not one per operator.** An agent that authenticates as itself is one `buyer_agent` principal however many operators it acts for; those operators are fields on its accounts. Operators are principals only when they authenticate themselves, which is the `require_operator_auth: true` model.
* **It grants no account authority.** Accounts are authorized through [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts), `list_accounts`, and the seller's own access controls, never by principal configuration.

For an end-to-end buyer-side example, see [Set up your buyer agent with a seller](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/principal-registration-walkthrough.md).

**Request schema:** [`/schemas/3.2.1/protocol/sync-principal-request.json`](https://adcontextprotocol.org/schemas/3.2.1/protocol/sync-principal-request.json)

**Response schema:** [`/schemas/3.2.1/protocol/sync-principal-response.json`](https://adcontextprotocol.org/schemas/3.2.1/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_identity` is 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.

Whether "the operator's identity" means individual users or one org-level service identity is the seller's authorization design. Separate credentials become one principal only through explicit administrative linking — never inferred from org membership, matching operator labels, or matching domains, because that inference is a cross-party takeover path. Sellers MUST NOT link, dedupe, or grant authority across principals based on matching operator identifiers.

If an interactive client presents only one shared service credential for all its users, the seller cannot isolate user-owned configuration — one user's sync could read or erase another's — so it MUST refuse to persist configuration under it until the client completes delegated authorization or account linking. Anonymous and shared-credential callers own nothing durable, at this layer or the account layer.

The model composes across hops. A buyer agent → Interchange → seller chain is two independent principal relationships: Interchange is the (agent) principal at the seller, and the buyer agent is the (agent) principal at Interchange. A person using Claude to buy through Interchange is an operator principal at Interchange. No hop sees through the next; each holds its own counterparty accountable, and end-to-end reconciliation happens at the data layer, where rows and partitions carry seller and account identity.

Request-body fields, a raw signing key ID, an API-key fingerprint, an access token, a session ID, and a bucket URL are not owner identifiers. A verified JWKS key proves which key signed a request; it does not by itself prove authority to act for an advertiser account. Key rotation or token renewal preserves the configuration only when the seller maps the new credential to the same stable principal.

For an RFC 9421 signed caller, the stable Agent subject is the canonical URL of the exactly matched operator `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.

For a Web Bot Auth signed caller (experimental), the stable Agent subject is the signer's [canonical origin](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/reference/url-canonicalization.md#canonical-origin), verified as [Web Bot Auth identities](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/by-layer/L1/security.md#web-bot-auth-identities) describes. One agent occupies one origin, so key rotation within the origin's directory preserves the principal. Whether a verified origin has a principal, and how it gets one, is [first contact and binding](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/by-layer/L2/accounts-and-agents.md#first-contact-and-binding).

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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-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:

1. **Authenticate.** Autonomous software presents its workload identity; a person presents their delegated user identity. No `register_buyer_agent` step exists or is needed.
2. **Read [`get_adcp_capabilities`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_adcp_capabilities).** Discover `adcp.principal`, its supported sections, event and delivery offerings, and limits.
3. **Verify the resolved identity.** Call [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal). A `recognized` result returns the existing durable `principal_id` and seller-resolved `principal_kind` without standing configuration. `unconfigured` means stable authentication succeeded but no durable record exists. The read creates neither records nor identifiers. When the result carries `principal_kind` (`current` or `recognized`), buyers MAY use it as a preflight before account calls: `buyer_agent` matches `account.require_operator_auth: false`, and `operator` matches `true`. A mismatch means the buyer presented the wrong credential for this seller's account model.
4. **Call `sync_principal`.** Declare consumption facts (`declarations`), register webhook subscribers (including `principal.changed` and, with explicit scope, account-anchored events), and register reusable reporting destinations.
5. **Authorize advertiser accounts separately** through [`request_account`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/request_account) or [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) and account-level tasks. Nothing in steps 1–4 grants account authority.
6. **Bind feeds.** Register one [reporting subscription](#reporting-subscriptions) per feed purpose that covers every authorized account, or bind each account's feeds to the reusable `destination_ref`s through `sync_accounts`.
7. **Observe readiness.** [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal) shows destination and declaration state; `principal.changed` keeps it fresh.

That yields four deliberately separate objects: the **credential** (seller's authorization system, out of AdCP scope), the **principal record** (this layer — one party's standing configuration), **account authorization** (which advertiser data the principal may access), and **delivery configuration** (where authorized data goes). A destination can be registered before any account exists, but it cannot receive account data until an account-scoped feed binding succeeds.

## Discovery

Only call this task when [`get_adcp_capabilities`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_adcp_capabilities) includes both:

```json theme={null}
{
  "adcp": {
    "principal": {
      "supported": true,
      "sync_task": "sync_principal",
      "read_task": "get_principal",
      "supported_sections": [
        "notification_configs",
        "reporting_destinations",
        "declarations"
      ],
      "caller_event_types": [
        "capabilities.changed",
        "principal.changed",
        "creative.status_changed",
        "account.change_recorded"
      ],
      "max_reporting_destinations": 16,
      "reporting_destination_offerings": [
        {
          "pattern": "file_transfer",
          "transports": ["s3"],
          "formats": ["parquet", "csv"],
          "verification_profiles": ["manifest_checksums", "canonical_digest"]
        },
        {
          "pattern": "dataset_share",
          "transports": ["delta_sharing"],
          "access_modes": ["databricks_to_databricks", "open_sharing"],
          "verification_profiles": ["native_commit"]
        }
      ],
      "suspension_interval_seconds": 900,
      "optimistic_concurrency": true
    }
  },
  "experimental_features": ["protocol.principal"]
}
```

Unsupported sections are rejected, not ignored. `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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-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:

| Section | Meaning |
| - | - |
| `notification_configs` | Caller-scoped webhook subscribers for caller-anchored events (`capabilities.changed`, `principal.changed`) and, with [fire-time authorization scoping](#principal-level-webhooks), account-anchored events. |
| `reporting_destinations` | Reusable, non-secret file, warehouse, or dataset-share destinations. |
| `declarations` | Buyer-declared [consumption facts](#declarations) — async payload versions, verifiable signing algorithms, experimental opt-ins — with a seller-computed accepted intersection. |
| `reporting_subscriptions` | Experimental (3.3). [Reporting feeds](#reporting-subscriptions) that cover every authorized account, or named accounts, from one registration. |

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](#suspension-and-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: true` returns `result.kind: validated` with 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.

When `expected_configuration_version` is present, the seller applies a new operation only if it equals the current opaque version. A stale version returns [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict) without mutation. This prevents two services using the same principal from silently overwriting each other. Read the current version with [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/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.changed` is the invalidation doorbell for seller-driven state transitions — a destination reaching `ready`, a setup action expiring, the accepted declarations intersection changing, a relationship status change or approaching expiry, a reporting subscription's `accounts_unauthorized` changing, an account's coverage exception opening or closing, or the subscription moving to `action_required` — and receivers repair by re-reading [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal).
* **Account-anchored events** (`creative.status_changed`, `account.change_recorded`, `product.*`, …) may be listed on caller-level subscribers, and require the explicit `all_authorized_accounts: true` acknowledgment — 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.
* **Account lifecycle.** A caller-level subscriber whose `event_types` include `account.status_changed`, with `all_authorized_accounts: true`, receives every authorized account's status transitions, such as `suspended` or `payment_required`, from one registration. Sellers that support this list `account.status_changed` in `principal.caller_event_types`.
* **Media-buy-anchored cadence reports** (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`) stay on each buy's `reporting_webhook` — they carry per-buy cadence choices this layer deliberately does not own. `impairment` is also media-buy-anchored but fires against `push_notification_config` — it is a dependency-state change on the buy, not a cadence report.

There is no wildcard. Callers enumerate `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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-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. The `declarations` 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.

```json theme={null}
{
  "idempotency_key": "8b54cf59-77f4-41ce-9f2a-6d1f0c8b1a77",
  "configuration": {
    "declarations": {
      "async_adcp_versions": ["3.2"],
      "webhook_signing_algorithms": ["ed25519"],
      "experimental_features": ["protocol.principal"]
    }
  }
}
```

Rules:

* Declarations are facts about consumption ability — never preferences or policy. [`get_adcp_capabilities`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_adcp_capabilities) stays objective seller facts; the intersection is the negotiated contract.
* Unknown `experimental_features` identifiers are accepted and simply excluded from the intersection, so one declaration works across sellers with different surfaces.
* An empty `webhook_signing_algorithms` intersection while any webhook subscriber is active fails the request with [`UNSUPPORTED_FEATURE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-unsupported-feature) — fires would be unverifiable.
* A later seller-side change to the accepted intersection fires `principal.changed` with reason `declarations_intersection_changed` and does not advance `configuration_version`.
* The readback persists the full negotiation record: the declared set, the accepted intersection, the `selected_async_adcp_version` the seller will actually use for pushes, and an `exclusions` entry 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.

```json theme={null}
{
  "idempotency_key": "528f1f06-e2a7-49b9-bd13-c953f35a1c49",
  "configuration": {
    "reporting_destinations": [
      {
        "pattern": "file_transfer",
        "destination_id": "reporting-archive",
        "active": true,
        "provider": { "domain": "object-store.example" },
        "transport": "s3",
        "location": "s3://pinnacle-reporting/adcp/",
        "accepted_formats": ["parquet"],
        "accepted_verification_profiles": [
          "manifest_checksums",
          "canonical_digest"
        ]
      },
      {
        "pattern": "warehouse_materialization",
        "destination_id": "analytics-warehouse",
        "active": true,
        "provider": { "domain": "data-warehouse.example" },
        "transport": "bigquery",
        "location": "pinnacle-analytics.adcp_reporting",
        "accepted_verification_profiles": [
          "native_commit",
          "canonical_digest"
        ]
      },
      {
        "pattern": "dataset_share",
        "destination_id": "daily-share",
        "active": true,
        "provider": { "domain": "data-platform.example" },
        "transport": "delta_sharing",
        "access_mode": "open_sharing",
        "recipient": {
          "identity": "reporting@example.com"
        },
        "accepted_verification_profiles": ["native_commit"]
      }
    ]
  }
}
```

Each destination declares one atomic delivery pattern. Register separate destination IDs when the same provider coordinates support materially different ownership or access paths.

The response returns a seller-issued `destination_ref` and one of these states:

| State | Meaning |
| - | - |
| `validating` | Automated ownership or access validation is in progress. |
| `ready` | The pattern-specific proof succeeded. The destination is eligible for a new authorized account/feed binding. |
| `action_required` | A typed provider-native setup action must be completed. The `setup` object names it. |
| `inactive` | The caller suspended the destination (`active: false`). New deliveries halt; existing caller-owned data is not deleted. |
| `rejected` | The coordinates or requested path cannot be used. |

`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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/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:

1. **Register.** The caller syncs the destination. The seller validates it against its advertised offering, issues `destination_ref`, and returns `state: "action_required"` with a typed `setup` object — 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.
2. **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.
3. **Poll the read task.** The caller polls [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal) until the destination reaches `ready`. Reading never mutates state or the version. Resubmitting the identical section under a fresh `idempotency_key` is also safe — it returns `action: "unchanged"` and preserves `destination_ref` — but the read task is the intended poll mechanism.
4. **Bind.** Account-scoped reporting configuration references the `ready` destination by `destination_ref`, subject to its own authorization.

What `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 not `ready`.
* **`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 produces `ready`, 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 that `destination_id` within its advertised `suspension_interval_seconds` and marks affected account bindings with a typed `destination_suspended` issue. Resubmitting with `active: true` lifts the suspension without new proof when nothing else changed.
* **Revoke** — omit the destination from the submitted `reporting_destinations` array (or submit `[]` to revoke all). Revocation has every suspension effect, removes the destination from current state, and retains its generations under `retired_destinations` for audit and reporting history. Reusing a retired `destination_id` requires fresh registration and proof and produces a new generation.

One suspension or revocation cuts delivery for every account bound to the destination — a compromised bucket does not require per-account unwinding. Neither operation deletes data already delivered, and neither revokes provider-side grants the caller manages in its own control plane.

### 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](https://github.com/adcontextprotocol/adcp/issues/6911)) supplies that second authorization by binding an account-scoped feed configuration to the reusable `destination_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.

## Reporting subscriptions

> [!NOTE]
>
> Experimental in 3.3 under `media_buy.reporting_subscriptions` ([RFC #8543](https://github.com/adcontextprotocol/adcp/issues/8543)). Use this section only when `supported_sections` includes `reporting_subscriptions` and `media_buy.reporting_delivery.configuration_tasks` includes `sync_principal`.

Account-level `reporting_delivery_configs` on [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) report nothing for a new account until someone configures it. A reporting subscription is registered once on the principal and follows the caller's account authority, the way a caller-level webhook with `all_authorized_accounts: true` does. The seller turns each subscription into per-account obligations, and buyers read them through [`get_reporting_status`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_reporting_status#subscription-coverage) as before.

```json theme={null}
{
  "idempotency_key": "0f4c1d2e-7b9a-4c3e-8f61-2d5a9b7e3c40",
  "configuration": {
    "reporting_subscriptions": [
      {
        "subscription_id": "analytics-daily",
        "subscription_version": 1,
        "active": true,
        "scope": { "all_authorized_accounts": true },
        "selector": {
          "feed_purpose": "analytics",
          "period_duration": "P1D",
          "required_finality": "official",
          "dimensions": ["package", "creative"],
          "match": "exact"
        },
        "reconciliation_mode": "delivery_only",
        "destination": {
          "destination_id": "reporting-archive",
          "path_template": "analytics/{account_id}/{period_start}"
        }
      }
    ]
  }
}
```

**Scope.** `scope` is exactly one of `all_authorized_accounts: true`, an explicit acknowledgment that is never implicit, or `account_ids[]`. `exclude_account_ids[]` opts accounts out of an all-accounts subscription. `operator_domains[]` keeps only accounts whose `operator` is listed. Use it whenever the destination belongs to one operator: an all-accounts subscription otherwise follows every account the principal is granted, including accounts a different operator grants later, and would send their data to that operator's destination. Registration requires no account authority and grants none.

An account is covered while the scope selects it and the principal is authorized for it, meaning its `authorization` object, when the seller advertises one, lists `get_reporting_status` in `allowed_tasks`. Within a covered account the subscription covers every confirmed media buy the principal can see through [`get_media_buys`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_media_buys), including buys created later. Buys created in the seller's UI or by other agents that the principal can't see are not covered. The seller re-checks authority at every delivery, including retries.

"Subscribe once" means once per principal. On a seller where each operator or advertiser grants its own credential (`require_operator_auth: true`), each credential is a separate principal and registers its own subscriptions.

**Selector.** Offering IDs are per seller and per product, and an all-accounts subscription can't name them for accounts that don't exist yet. The subscription selects by attributes that are stable across sellers:

| Field | Meaning |
| - | - |
| `feed_purpose` | `pacing`, `analytics`, or `billing`. |
| `period_duration` | ISO 8601 duration, matched exactly against the offering's `schedule.period_duration`. |
| `required_finality` | `snapshot` or `official`, which the offering must list in `supported_finality`. |
| `alignment` | Optional. Only offerings whose schedule uses this alignment (`utc`, `account_timezone`, `source_timezone`, `billing_cycle`) are candidates. |
| `dimensions` | Optional breakdown in the standard vocabulary: `package`, `creative`, `country`, `region`, `metro`. The vocabulary is extensible: an unknown value matches no offering. |
| `match` | `exact` picks the offering whose dimension set equals `dimensions`. Native dimensions that don't map to the vocabulary count as distinct, so `package × device` does not equal `package`. `finest` picks the offering with the most mapped dimensions among those that include `dimensions`. Ties under `exact` and `finest` go to the lowest `offering_id`. `all` picks every offering that includes `dimensions`, each with its own obligations. Defaults to `exact` when `dimensions` is present and `finest` when it is absent. Billing subscriptions SHOULD use `exact`. |
| `period_timezone` | Optional IANA timezone override, only when `account_ids` names exactly one account. |

For example, if a seller offers `package`, `package × creative`, and `package × country` breakdowns, `dimensions: ["package"]` resolves to the `package` offering under the default `exact`; `finest` would pick one of the two finer offerings by `offering_id`, and `all` would pick all three.

`reconciliation_mode` must also match the offering. Without a `destination` the subscription selects only API-delivered (Core) offerings; with one, only offerings whose `method` matches that destination's pattern and transport. Billing subscriptions require `official` finality and `consumer_receipt`, and `consumer_receipt` requires a destination. The seller resolves the selector separately for each account and each buy's products. Each (buy, resolved offering) pair is owed its own periods, and each obligation names its `offering_id` and `report_definition_id`. An account or buy with no matching offering gets an `offering_unsupported` exception. When only some of a buy's packages resolve, the buy is covered for those, and the exception names the uncovered `package_ids`. Neither case fails the request or the subscription.

**Period boundaries.** The resolved offering's published schedule governs period boundaries: its alignment (account timezone, UTC, an upstream source timezone, or a billing cycle), anchor, and timezone, which the buyer reads from the offering. Use the selector's `alignment` to require one. `period_timezone` overrides the timezone for a subscription scoped to exactly one account. When an account's timezone changes, for example through a [`request_account`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/request_account) change, account-timezone periods switch at the first old-timezone boundary at or after the change. The switch is recorded in `get_reporting_status` as a `timezone_cutovers` entry, and [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) shows the new `timezone` and `revision`.

**Destinations.** `destination.destination_id` names an entry in this principal's `reporting_destinations`, which must still exist after the same request applies. Every delivered row carries `account_id`. A `path_template` is a relative path with no `.` or `..` segments; allowed placeholders are `{account_id}`, `{subscription_id}`, `{offering_id}`, and `{period_start}`. Each substituted value fills exactly one path segment: the seller percent-encodes (RFC 3986) any value that contains `/` or equals `.` or `..`. It must contain `{account_id}` when the scope can cover more than one account, and is then required for `file_transfer` destinations. When more than one active subscription of the principal shares a destination, every template must contain `{account_id}` and the templates must differ. When an agency must keep advertisers apart, an account-scoped subscription points that advertiser's feed at its own destination; sellers that support subscriptions MUST support that.

**Coverage.** The buyer computes coverage without asking. For each covered buy and resolved offering, the owed periods are the complete periods of the offering's schedule that:

1. start on or after the first boundary at or after the latest of the subscription version's `accepted_at` (in the readback), the buy's `confirmed_at`, and the account's `authorization.authorized_at` on `list_accounts`; and
2. overlap the buy's flight, from its `start_time` to its stop time. The stop time is the earliest of `end_time`, `cancellation.canceled_at`, and `completed_at`.

A buy confirmed on November 1 with a December 1 start owes nothing before December 1. Flight values are taken as of each period's `scope_resolved_at`. So an [`update_media_buy`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/update_media_buy) extension adds only periods not yet resolved, and reinstating or extending a buy never makes an elapsed period owed after the fact. The period containing the stop time is owed in full. Timing (`expected_at`) comes from each obligation's offering. The seller reports only exceptions, and each removes periods; none adds one. See [`get_reporting_status`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_reporting_status#which-periods-are-owed).

**Versions and cutovers.** A new `subscription_version` is required whenever delivery semantics or scope change: the selector, `reconciliation_mode`, destination, `scope`, `exclude_account_ids`, or `operator_domains`. Each version has its own `accepted_at`. `active` and `replace_account_configs` change in place. Changes are **cutovers** at the instant they take effect, T. The outgoing schedule owes its periods that start before T, in full, ending at E. The incoming schedule owes its periods that start at or after E. No period is owed twice. When the two schedules' boundaries don't align, the stretch from E to the incoming schedule's next boundary is owed under neither; it is shorter than one incoming period. Version changes (for accounts in both versions, T is the new `accepted_at`), deactivation, overrides, `replace_account_configs`, offering resolution changes, and account timezone changes all use this rule. See [cutovers](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_reporting_status.md#which-periods-are-owed).

Obligations from a subscription carry `delivery_config_id` equal to `subscription_id` and `delivery_config_version` equal to `subscription_version`. Subscription IDs and the principal's `sync_accounts` `delivery_config_id` values therefore share one namespace, and the seller rejects a collision. A `subscription_id` is never `.` or `..`.

The section is complete desired state like the others. Omitting a subscription, or setting `active: false`, deactivates it as a cutover at that moment: periods already started stay owed in full and no later ones are created. Deactivated subscriptions stay readable in [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal) as `inactive` while their obligations are retained. The seller rejects the whole request with [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request) when two active subscriptions at the same scope level would both apply to one account and feed purpose.

**Precedence.** Precedence is per account and `feed_purpose`, and only among the principal's own registrations. During 3.x the principal's own active `sync_accounts` `reporting_delivery_configs` entry for the account replaces every one of its subscriptions for that account and purpose, whether all-accounts or `account_ids`. Otherwise its `account_ids` subscription naming the account replaces its all-accounts subscription. Another principal's configurations and subscriptions never affect this principal's subscriptions, and `overridden_by` never names them. A replaced subscription records an `overridden` exception naming the overriding `delivery_config_id` or `subscription_id`, so a buyer never gets two feeds and two sets of obligations for one account and purpose.

To end its own account-level overrides without calling `sync_accounts`, a principal sets `replace_account_configs: true` on the subscription. The seller applies it once per `subscription_version`, or when the flag changes to `true`, not on every resend. It deactivates the principal's own active `reporting_delivery_configs` with the same `feed_purpose`, only on in-scope accounts where the subscription resolves an offering. It never touches accounts the subscription excludes, or with `offering_unsupported` or `capacity`. Each configuration is cut over to the subscription: the configuration keeps the periods it started, and the subscription owes its periods from the end of the last of them. `sync_accounts` and [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) readback show the configuration inactive, and the `overridden` exception ends at the cutover. A later `sync_accounts` entry takes precedence again. Subscription writes, including these deactivations, are caller configuration, not account changes: they never advance the account's `revision`, so they never conflict with an in-review `request_account` change.

**Seller limits.** A seller may declare `media_buy.reporting_delivery.subscriptions.max_accounts`. Beyond it, or for an account it won't serve, the seller records an `excluded` exception with reason `capacity` or `action_required` (with a pending action), rather than being forced to serve every account. The served set is deterministic and reported, never silently partial.

**Exceptions without polling.** [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal) lists, per subscription, `accounts_with_exceptions[]`: each account with an open exception and its reasons. An agency doesn't have to poll `get_reporting_status` account by account. `principal.changed` with reason `reporting_subscription_changed` fires whenever an account's exception opens or closes.

**Lost authority.** When the principal loses authority for an account the subscription covered, `get_principal` lists it under that subscription's `accounts_unauthorized[]` with `unauthorized_at`, `last_owed_period_end`, and the count of withheld periods. The entry stays until re-grant or for `status_retention_days`. After that it drops, and a later re-grant is a fresh coverage start from the new `authorized_at`, with no gap claimed. Each addition or removal fires `principal.changed` with reason `reporting_subscription_changed`, as does a subscription moving to `action_required`. [`get_reporting_status`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_reporting_status) treats the account as unknown, as for any unauthorized caller.

How coverage starts and ends, and what happens on revocation and re-grant, is in [`get_reporting_status`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/media-buy/task-reference/get_reporting_status#subscription-coverage).

## 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.

This is an allowed set, not a claim that every feed may use the weakest member. The selected seller offering and account-level feed configuration choose one acceptable profile.

## 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 on `location` 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](#destination-lifecycle) 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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/sync_agent_notification_configs). Implementations exposing both tasks maintain one underlying caller-scoped subscriber set.

## Errors

Failed responses carry `result.kind: "failed"` with structured errors and deliberately cannot carry a principal ID, version, or configuration state.

| Code | When | Recovery |
| - | - | - |
| [`AUTH_REQUIRED`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-auth-required) | Anonymous or unstable caller identity. | Authenticate as a stable principal, then retry. |
| [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request) | Schema violation, duplicate `destination_id` or `subscription_id`, credential-shaped `location` or recipient, oversize section, a subscription naming a missing destination or reusing a `delivery_config_id`, or two active subscriptions at one scope level for the same account and feed purpose. | Fix the request; do not retry unchanged. |
| [`UNSUPPORTED_FEATURE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-unsupported-feature) | Section not in `supported_sections`, destination outside `reporting_destination_offerings`, subscriber event type outside `caller_event_types`, empty signing-algorithm intersection with an active subscriber, or `expected_configuration_version` sent to a seller advertising `optimistic_concurrency: false`. | Re-read capabilities and select from the advertised offering. |
| [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict) | `expected_configuration_version` is stale. Nothing was mutated. | Read the current version with [`get_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_principal), reconcile, and resubmit under a new `idempotency_key`. |
| [`IDEMPOTENCY_CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-idempotency-conflict) | An `idempotency_key` was reused with a different body. | Use a fresh key for the new logical operation. |
| [`IDEMPOTENCY_IN_FLIGHT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-idempotency-in-flight) | The original operation for this key is still executing. | Retry the identical request after a short delay. |
| [`IDEMPOTENCY_EXPIRED`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-idempotency-expired) | The retention window for this key elapsed. | Read current state, then resubmit as a new operation if still needed. |

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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/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`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_adcp_capabilities) into caller-specific state. Capabilities remain cacheable seller facts; principal configuration remains authenticated caller state.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.