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

# Accounts and agents

> AdCP accounts and agents: the four entities in every transaction (brand, account, operator, agent), account-id namespaces, buyer-declared accounts, and billing configuration.

AdCP distinguishes four entities in every billable operation:

| Entity | Question | How identified |
| - | - | - |
| **Brand** | Whose products are advertised? | Brand reference: `domain` + optional `brand_id` and commercial-identity `countries[]` ([brand.json](/dist/docs/3.3.0-beta.0/brand-protocol/brand-json)) |
| **Account** | Who gets billed? What rates apply? | [Account reference](#account-references) |
| **Operator** | Who operates on the brand's behalf? | Domain (e.g., `pinnacle-media.com`) |
| **Agent** | What software is placing the buy? | Authenticated session |

**Brand** — The advertiser whose products or services are promoted. Identified by a `brand` reference (`domain` + optional `brand_id` and ISO country `countries[]`), resolved via `/.well-known/brand.json`. Countries distinguish commercial advertiser identities without becoming delivery targeting.

**Account** — A billing relationship between a buyer and seller. Determines rate card, payment terms, credit limit, operational timezone, and who receives invoices. Every billable operation requires an account reference — a seller/storefront-assigned `account_id` when the seller or upstream platform owns the canonical account namespace, or an advertiser natural key (`brand`, `operator`, optional `operator_unit`, fixed `currency`, buyer-selected `timezone`, `sandbox`) for buyer-declared accounts.

**Operator** — The entity driving buys — an agency trading desk, the brand's internal team, or another entity acting on behalf of the advertiser. Identified by domain and verifiable via [authorized operators](#authorized-operators) in `brand.json`.

**Agent** — The software placing buys and managing campaigns. Authenticates with the seller and may operate on behalf of multiple operators and brands.

See [Accounts Protocol overview](/dist/docs/3.3.0-beta.0/accounts/overview) for the full commercial model and [sync\_accounts](/dist/docs/3.3.0-beta.0/accounts/tasks/sync_accounts) for the task reference.

## What sellers declare

Sellers configure the `account` section of [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities#account):

**1. Which billing models do you support?** (`supported_billing`)

The buyer must pass one of these values as `billing` in every `sync_accounts` entry. The seller either accepts or rejects.

| Billing | Who is invoiced | Use case |
| - | - | - |
| `operator` | Operator (agency or brand buying direct) | Operator buying on their own terms |
| `agent` | Agent | Agent consolidates billing across brands |
| `advertiser` | Advertiser directly | Operator places orders but advertiser pays (common on social platforms and in DACH B2B workflows) |

This is an account-level invoiced-party choice. It does not select a payment rail, clearing intermediary, or settlement route for an individual media buy; AdCP payment and settlement remain out of protocol.

**2. Which account currency models do you support?** (`supported_account_currency_modes`)

Sellers implementing AdCP 3.2 advertiser-account provisioning advertise one or both values. The field remains optional in the shared 3.x wire schema so existing 3.0 and 3.1 responses continue to validate; absence means the older seller's currency model is not discoverable and does not imply support for either mode.

| Mode | Provisioning behavior |
| - | - |
| `fixed` | Include one immutable ISO 4217 `currency`; it participates in the advertiser account natural key. |
| `per_media_buy` | Omit account `currency`; select currency on each proposal or media buy. |

When both are present, the buyer selects the model by including or omitting `currency`.

**3. Do you require operator-level auth?** (`require_operator_auth`)

This field declares who holds the credential you authorize — the buyer agent or each operator — and with it the account reference shape. It does not mean the operator named in a natural key authenticates: with `false`, only the agent does.

When `false` (default) — **buyer-declared accounts**: the seller trusts the agent. The agent authenticates once and declares accounts via `sync_accounts`. On subsequent requests, the buyer passes the natural key (`brand` + `operator`) and the seller resolves internally. See [Seller walkthrough: buyer-declared accounts](#seller-walkthrough-buyer-declared-accounts) for how the seller identifies the agent and where each field comes from.

When `true` — **account-id namespaces**: each operator must authenticate with the seller directly. The agent obtains a credential per operator — via OAuth using the seller's `authorization_endpoint`, or via API key out-of-band. Subsequent requests pass a seller-assigned `account_id`. If a credential may access more than one account, the seller MUST expose `list_accounts` and the buyer MUST resolve an explicit account before the first account-scoped request. If a credential is bound to exactly one account, the seller SHOULD expose `list_accounts` returning that singleton; a seller MAY omit `list_accounts` only when it supplies the same explicit account ID through another declared path or out-of-band onboarding.

**4. Is your `account_id` a stable reference?** (`stable_account_id`)

Declare `true` when you return `account_id` on every non-failed `sync_accounts` provisioning result, accept `{ "account_id": "..." }` on every account-scoped task for every account the caller can operate, and never change an account's ID, including across identity reconciliation. Buyers can then send the ID instead of the natural key, which they keep only to open accounts and to look IDs up again. Buyer-declared sellers keep accepting the natural key throughout 3.x, but natural-key references after provisioning are deprecated and planned for removal in 4.0, so declare this capability during 3.x. See [Toward account IDs everywhere](/dist/docs/3.3.0-beta.0/accounts/overview#toward-account-ids-everywhere).

For sandbox, the path follows the account namespace: account-id namespaces use pre-existing test accounts from `list_accounts` or out-of-band setup; buyer-declared accounts use `sync_accounts` with `sandbox: true` and reference by natural key.

Sellers can also declare `account_financials: true` to expose account-level financial data (spend, credit, invoices) via [`get_account_financials`](/dist/docs/3.3.0-beta.0/accounts/tasks/get_account_financials). This only applies to operator-billed accounts.

**Example capabilities:**

```json theme={null}
{
  "account": {
    "require_operator_auth": false,
    "supported_billing": ["operator", "agent"],
    "supported_account_currency_modes": ["fixed", "per_media_buy"]
  }
}
```

Sellers that support `advertiser` billing declare it explicitly:

```json theme={null}
{
  "account": {
    "require_operator_auth": false,
    "supported_billing": ["operator", "agent", "advertiser"],
    "supported_account_currency_modes": ["fixed", "per_media_buy"]
  }
}
```

These fields combine into common patterns.

## Seller patterns

Which kind of platform are you buying from? That determines the account setup pattern.

| Platform type | Account pattern | `require_operator_auth` | `supported_billing` |
| - | - | - | - |
| [Social / walled garden](#social-platform) | Upstream-managed account-id namespace | `true` | `["operator"]` |
| [Direct publisher](#direct-publisher) | Buyer-declared accounts | `false` | `["operator"]` or `["operator", "agent"]` |
| [DSP / programmatic](#dsp--programmatic) | Buyer-declared accounts | `false` | `["agent"]` |

### Social platform

The operator already has an account on the platform — an ad account, a business manager, a self-serve dashboard. The upstream platform owns the canonical account namespace accessible to that credential. The agent obtains the operator's credentials (via OAuth or API key), opens a per-operator session, resolves an explicit account through `list_accounts`, and uses the returned `account_id` values. The platform bills the operator directly.

**Capabilities:**

```json theme={null}
{
  "account": {
    "require_operator_auth": true,
    "supported_billing": ["operator"],
    "supported_account_currency_modes": ["fixed"],
    "authorization_endpoint": "https://seller.example.com/oauth/authorize"
  }
}
```

**Buyer workflow:**

1. Call `get_adcp_capabilities` — see `require_operator_auth: true` and `authorization_endpoint`
2. For each operator:
   a. Obtain operator's credential (OAuth via `authorization_endpoint`, or API key out-of-band)
   b. Open a new session with the operator's credential
   c. Call `list_accounts` to discover accounts visible to that credential
3. Human or policy selects the correct account from the list
4. Call `get_products` / `create_media_buy` with the operator's session and `{ "account_id": "..." }`

**list\_accounts response:**

```json theme={null}
{
  "accounts": [{
    "account_id": "acc_spark_social_001",
    "name": "Spark paid social",
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com",
    "status": "active",
    "billing": "operator",
    "account_scope": "operator_brand"
  }]
}
```

Subsequent calls use the discovered ID:

```json theme={null}
{
  "account": { "account_id": "acc_spark_social_001" }
}
```

**Key point:** The operator's credential — not the agent's — authorizes all calls in that session. The buyer does not declare or create accounts through AdCP in the 3.0.x account-id model; `list_accounts` mirrors the upstream namespace. If the seller exposes `sync_accounts`, it is only for settings updates against an existing `account_id`, not natural-key provisioning, unless a future explicit capability declares account-id provisioning.

### Direct publisher

The publisher trusts the agent but bills the operator directly. The agent sets up accounts via `sync_accounts` — no per-operator login needed. Accounts may require human approval (credit checks, legal agreements) before becoming active.

Many publishers also accept agent billing (`supported_billing: ["operator", "agent"]`). The buyer chooses per account — operators with a direct relationship use `billing: "operator"`, everything else uses `billing: "agent"`. If the seller doesn't support the requested billing for a particular account, it rejects the request and the agent re-submits with a different model.

**Capabilities:**

```json theme={null}
{
  "account": {
    "supported_billing": ["operator", "agent"],
    "supported_account_currency_modes": ["fixed", "per_media_buy"]
  }
}
```

**Buyer workflow:**

1. Call `get_adcp_capabilities` — see `require_operator_auth` absent (defaults to `false`)
2. Call `sync_accounts` for each brand/operator pair
3. Wait for account status `active` — may require human to complete credit/legal at `setup.url`
4. Call `get_products` with `account` reference
5. Call `create_media_buy` with `account` reference

**sync\_accounts request — brand buying direct:**

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "billing": "operator"
  }]
}
```

Seller acknowledges the request but requires setup before provisioning:

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "action": "created",
    "status": "pending_approval",
    "billing": "operator",
    "account_scope": "brand",
    "setup": {
      "url": "https://seller.example.com/advertiser-onboard",
      "message": "Complete advertiser registration and credit application"
    }
  }]
}
```

The seller has acknowledged the relationship `(brand: "acme-corp.com", operator: "acme-corp.com", billing: "operator")`, but the account is pending review before it becomes active. A human at Acme Corp completes the setup at the URL. To check progress, the agent either:

* Re-calls `sync_accounts` with the same natural key — the seller returns the updated status
* Receives a webhook notification if `push_notification_config` was provided in the request

**Key point:** `pending_approval` is the normal path. Every buyer needs a direct relationship with the seller.

**Billing rejection — operator billing not available:**

The seller supports operator billing in general, but may not support it for every operator. Here, the agent requests operator billing for an operator without a direct relationship:

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "billing": "operator"
  }]
}
```

Seller rejects the request because this operator has no direct billing relationship:

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "action": "failed",
    "status": "rejected",
    "errors": [{
      "code": "BILLING_NOT_SUPPORTED",
      "message": "Operator billing is not available for this account. Re-submit with billing: \"agent\"."
    }]
  }]
}
```

The agent re-submits with `billing: "agent"` or informs the buyer that operator billing is not available with this seller. Billing is never silently remapped.

### DSP / programmatic

All billing flows through the agent. The agent has a standing relationship with the platform and consolidates billing across all brands and operators. Accounts are created instantly — no human approval needed.

**Capabilities:**

```json theme={null}
{
  "account": {
    "supported_billing": ["agent"],
    "supported_account_currency_modes": ["per_media_buy"]
  }
}
```

**Buyer workflow:**

1. Call `get_adcp_capabilities` — see `supported_billing: ["agent"]`
2. Call `sync_accounts` for each brand/operator pair with `billing: "agent"`
3. Accounts are active immediately — no human approval needed
4. Call `get_products` / `create_media_buy` with `account` reference

**sync\_accounts request:**

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com",
    "billing": "agent"
  }]
}
```

Account active immediately:

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com",
    "action": "created",
    "status": "active",
    "billing": "agent",
    "account_scope": "operator_brand"
  }]
}
```

**Key point:** The agent receives a single consolidated invoice. Per-brand accounts give reporting granularity but billing is centralized.

## Authorized operators

Brands declare who can represent them in `/.well-known/brand.json` via the `authorized_operators` field. Sellers SHOULD verify operators against this when processing `sync_accounts`.

```json theme={null}
{
  "house": {
    "domain": "nova-brands.com",
    "name": "Nova Brands"
  },
  "brands": [
    { "id": "spark", "names": [{"en": "Spark"}] },
    { "id": "glow", "names": [{"en": "Glow"}] }
  ],
  "authorized_operators": [
    {
      "domain": "pinnacle-media.com",
      "brands": ["spark", "glow"],
      "countries": ["US", "GB", "DE"]
    },
    {
      "domain": "summit-agency.jp",
      "brands": ["spark"],
      "countries": ["JP"]
    },
    {
      "domain": "nova-brands.com",
      "brands": ["*"]
    }
  ]
}
```

| Field | Required | Description |
| - | - | - |
| `domain` | Yes | Operator's domain |
| `brands` | Yes | Brand IDs this operator can represent. `["*"]` means all brands. |
| `countries` | No | ISO 3166-1 alpha-2 country codes. Omit for global authorization. |

### Verification flow

1. Resolve `{brand.domain}/.well-known/brand.json`
2. Check `authorized_operators` for matching `domain` with the brand in `brands`
3. If found → proceed (account may still need credit/legal approval)
4. If not found → reject the account (`action: "failed"`) or return `pending_approval` for manual review

A `brand.json` listing is sufficient, not necessary. Finding the operator lets the seller provision without further review. If the operator isn't listed, the request alone does not authorize it: the seller either rejects the account or holds it in `pending_approval` until its own review confirms the operator out of band. Either way, the brand-operator check below passes only through the listing or that review.

**Self-authorization is implicit.** When the `operator` domain matches the brand's domain, the brand is operating directly — no listing in `authorized_operators` is needed.

`authorized_operators` models the interface between the brand and whoever operates on its behalf. Grants in trust.json model one agency hop; see below.

### Grants in trust records (experimental)

From 3.3, a brand's house can publish demand-side grants in its `/.well-known/trust.json` ([trust.json](/dist/docs/3.3.0-beta.0/reference/trust-json), [DR-0025](https://github.com/adcontextprotocol/adcp/blob/main/governance/decisions/DR-0025-demand-side-grants-in-trust-json.md), [#6033](https://github.com/adcontextprotocol/adcp/issues/6033)). A grant says "this organization, optionally only these of its agents, may act for us, for these scopes, in these countries, during this window". It is the trust.json successor to `authorized_operators`.

```json theme={null}
{
  "grants": [
    {
      "grantee": "pinnacle-agency.example",
      "scopes": ["adcp:media_buying"],
      "agents": [{ "url": "https://buyer.pinnacle-agency.example/mcp" }],
      "profiles": { "adcp": { "brands": ["nova_motors"] } },
      "valid_until": "2027-06-30T00:00:00Z"
    }
  ],
  "exclusive_grants": true
}
```

This section is the normative home for evaluating grants. Spec R6 and DR-0025 record the design and point here.

**How a seller evaluates acts-for authority:**

* **`grantee` is a registrable domain.** A grant's `grantee` MUST be a registrable domain under the pinned Public Suffix List snapshot from step 1 of [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities) (spec R3a step 1). A grant whose `grantee` is not one covers no claim, and its `scopes` still count for `exclusive_grants`. To limit a grant to some of the grantee's agents, list them in `agents[]`.
* **Domains compare as registrable domains.** `account.operator` and the brand's domain are compared with `grantee`, with the signer's operator, and with each other, as registrable domains under the same snapshot.
* **One record authorizes: the house's.** The house is the one the brand's brand.json names, as the [mutual-assertion trust model](/dist/docs/3.3.0-beta.0/brand-protocol/brand-json#mutual-assertion-trust-model) resolves it, and otherwise the brand's domain itself. The seller reads the house's record at its registrable domain, `https://{eTLD+1(house)}/.well-known/trust.json`, as step 1 of [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities) derives the operator's record. Grants in any other record are not consulted, including a record at the brand's own domain when the model resolves a house, so the two never compete. The buyer's operator record never authorizes a brand outside the operator's registrable domain.
* **Implicit authority.** When `account.operator` and the signer's operator are the same registrable domain as the brand's domain or its house, authority is implicit and no grant is needed. This extends self-authorization above, which compares the `operator` domain with the brand's domain exactly, to the registrable domain, because the signer's operator is a registrable domain, and to the house, because the house's record is the one that authorizes and an organization's own agents act for it without a grant.
* **Otherwise a grant chain must reach the signer's operator** with a scope covering the operation (`adcp:media_buying` for buys). Any of these satisfies it:

  * the house grants the signer's operator, and `account.operator` is the signer's operator;
  * the brand is `account.operator` and its house grants the signer's operator (a brand that runs its own account through an agent a platform hosts);
  * the house grants `account.operator`, and `account.operator`'s own trust.json, read at its registrable domain, grants the signer's operator (an agency using an intermediary, or a platform hosting the agency's agent).

  `account.operator` MUST be on the chain. A grantee never extends a grant it received: the second link is its own grant, and the intermediary cannot grant further.
* **`agents[]` narrows the link that reaches the signer.** On any link that carries `agents[]`, the signer's [canonical origin](/dist/docs/3.3.0-beta.0/reference/url-canonicalization#canonical-origin) MUST be listed, or the chain ends at that link. A grant's `agents[]` names agents its grantee runs, so a house grant to `account.operator` that is narrowed to the agency's own agents never covers an intermediary's agent: a two-link chain needs an unnarrowed first link, and the narrowing goes on `account.operator`'s grant to the intermediary.
* **Every link must cover the account, and effective authority is the intersection along the chain:**
  * `profiles.adcp.brands` covers `account.brand.brand_id` (`["*"]` or an omitted list covers every brand the grantor owns). A brand reference with no `brand_id` is covered only by `["*"]` or an omitted list;
  * `countries` covers every value in `account.brand.countries`. A grant with no `countries` is global; a brand reference with no `countries` is covered only by a global grant;
  * `scopes` covers the operation;
  * the window runs from the latest `valid_from` to the earliest `valid_until` along the chain, and the check time falls inside it.
* **Absence.** `exclusive_grants: true` makes `grants[]` exhaustive for the scopes it names. Without it, an acts-for claim no grant covers stays at the seller's discretion: its own onboarding records, never the house's brand.json.
* **Fetch failure.** A grantor's trust record that can't be fetched is never absence and never `grant_absent`; the outage rules in [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities) apply to a grantor's record as to the operator's.
* **No downgrade.** A 200 from the house's trust.json makes it the only source of grants, as the Coexistence rules in [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities) set out. Through 3.x, `authorized_operators` in brand.json stays a valid source only for a house that has never published a trust.json.
* **Freshness.** Grants are checked at `sync_accounts` and on the freshness schedule in [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities).

**When no grant covers the claim,** the request fails with `PERMISSION_DENIED` and `error.details` `{ "scope": "agent", "reason": "grant_absent" }`. It is emitted only once the signer's identity is established, under the cross-tenant oracle clamp in [Per-Agent Authorization Gate](/dist/docs/3.3.0-beta.0/building/by-layer/L3/error-handling#per-agent-authorization-gate). It is a request-level authorization failure: no account is created, and no account changes status. Grants are public, so the reason discloses nothing the buyer can't already read. Like every authorization outcome for a signer whose identity is established, it uses the AdCP error envelope, never a Web Bot Auth status ([Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities)).

**When a grant is withdrawn,** the agent loses access, not the account. Later calls from that agent fail with `grant_absent`. The account keeps its status and its live buys, and the operator can authorize a different agent for it. `suspended` stays a seller-initiated pause of the account itself.

## Buyer-agent identity

`authorized_operators` tells the seller whether an operator is allowed to represent a brand. It doesn't tell the seller who the *agent* placing the call is, or what commercial relationship is on file with that agent. Those are different questions, and the seller checks both before provisioning.

Two layers run on every `sync_accounts` request:

| Layer | Question | Where it lives |
| - | - | - |
| **Agent identity** | Which buyer agent issued this request? | The seller's onboarding record, looked up by signed-request `agent_url` (when [request signing](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#signed-requests-transport-layer) is in use) or by the bearer / API-key / OAuth credential the agent presented. Signature or credential establishes identity; authorization is a separate check. |
| **Brand-operator authorization** | Is the operator named in the request authorized to act for the brand? | `brand.json` `authorized_operators` (above). Verified whether the request is signed or not. |

Both layers MUST pass before the account becomes active. A signed request from an onboarded agent for an unlisted operator gets rejected or held for review on the brand-operator check; a request from an unrecognized agent for an authorized operator gets rejected on the identity check. Sellers that advertise [`request_signing.required_for`](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#transport-scope) on `sync_accounts` reject unsigned traffic at the identity layer; sellers that don't advertise it MAY still require an established credential mapping before agent-billable values are accepted.

The brand-operator check runs against the seller's cached `brand.json` per [Operator revocation and caching](#operator-revocation-and-caching) — revocation is eventual. Sellers performing high-value or first-time-on-this-brand provisioning SHOULD bypass the cache to close the TOCTOU window.

**SDK naming for the brand-operator authorization Protocol.** SDKs that surface a typed Protocol for the brand-operator check (for adopters to plug their own resolver into) SHOULD name it after the file consulted: `BrandAuthorizationResolver` (or equivalent in idiomatic casing). The file is `brand.json/authorized_operators` — the brand-side declaration of who may represent the brand. SDKs SHOULD NOT name this Protocol after `adagents.json`, which is publisher-side / data-provider-side and models a different relationship (which sales agents may sell that publisher's inventory). Naming the buyer-side resolver `AdagentsResolver` confuses the two surfaces and locks adopters into the wrong mental model. This is a spec-side recommendation; SDK conventions track upstream.

**The agent's commercial state is offline.** Whether a buyer agent is *passthrough-only* (no payments relationship — only the operator can be invoiced) or *agent-billable* (the agent can be invoiced directly) is recorded in the seller's onboarding system, the same way operator account creation is. Provisioning that record — contract, KYC, payment terms, billing entity capture — is out of scope for AdCP. What's in scope is two on-wire consequences:

1. **Runtime billing gate.** A passthrough-only agent that submits `billing: "agent"` or `billing: "advertiser"` is rejected with `BILLING_NOT_PERMITTED_FOR_AGENT` and an `error.details.suggested_billing` of `operator`. See [Billing and Account Setup](/dist/docs/3.3.0-beta.0/building/by-layer/L3/error-handling#billing-and-account-setup) for the recovery contract.
2. **Per-agent defaults.** Sellers MAY pre-fill `payment_terms`, `billing_entity`, rate-card linkage, and credit limit from the buyer agent's onboarding record when provisioning new accounts under that agent. Per-account values on the `sync_accounts` request always take precedence over per-agent defaults — the buyer can override per row. The per-agent layer is a recommended implementation pattern (it mirrors how SSPs maintain `buyer_id` / `seat_id` rows for OpenRTB DSPs); smaller publishers MAY collapse to seller-wide defaults until they have receivables ops that distinguish per-agent terms.

### First contact and binding

> [!NOTE]
>
> **Experimental (Web Bot Auth profile, 3.3).** Under the Web Bot Auth signing profile, a seller can identify a buyer agent it has never onboarded: the signed request names the signer's origin, and the seller verifies it against that origin's key directory ([Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities)). These rules govern what happens next. This is rule R5 in `specs/agent-identity-3-3.md` ([#8118](https://github.com/adcontextprotocol/adcp/issues/8118)); relationship status (R8) is on [`get_principal`](/dist/docs/3.3.0-beta.0/protocol/get_principal#relationship-status).

1. **Verification alone never creates a principal.** A verified origin with no binding is *identity-established*. It gets what an anonymous caller gets, reads included, plus [`get_principal`](/dist/docs/3.3.0-beta.0/protocol/get_principal), which returns `unconfigured`. A principal comes only from a binding under rule 2, including the binding a seller creates when it approves a new identity.
2. **A binding is an explicit, audited seller record** from origin to principal. The seller makes it only after the operator's trust record lists the origin with the required role.
3. **An origin maps to at most one principal.**
4. **Binding onto an existing RFC 9421 principal needs explicit approval.** A seller binds a Web Bot Auth origin onto an existing `agents[].url` principal only when the origin is unambiguous, and only with explicit out-of-band approval. In the RFC 9421 profile, controlling the agent's host was not enough to obtain its keys, and this rule keeps origin control alone from inheriting an existing principal. This binding is never automatic.

**Automatic binding.** Where the seller declares an `open` onboarding mode, `sync_principal` from an identity-established caller MAY create a **new** principal, limited to the open modes. Anything else waits for the seller's approval. `get_principal` never creates a principal.

**What the buyer sees while it waits.** Until the seller binds it, the caller stays identity-established. An operation that needs a binding fails with `PERMISSION_DENIED` with no `details`, the code [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities) names for a failed trust-record evaluation. Once the seller has an application in progress, `get_principal` carries `relationship` with `status: "pending_approval"` and, when the seller has one, an applicant-specific `setup` step. When the relationship becomes active, is suspended or blocked, or approaches its `expires_at`, `principal.changed` fires (`relationship_changed`, `relationship_expiring`) and the buyer re-reads `get_principal`. See [Relationship status](/dist/docs/3.3.0-beta.0/protocol/get_principal#relationship-status).

**After binding**, the two layers above still apply on every `sync_accounts`: agent identity through the binding, and brand-operator authorization. A binding is a cache of the trust-record evaluation. Its freshness, the outage rules, and what a delisted origin keeps are in [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities).

## Account references

Every account-scoped operation accepts an `account` object instead of a flat `account_id` string. The seller's `require_operator_auth` capability determines the auth model and reference shape; tool exposure distinguishes upstream-managed `account_id` namespaces from seller-defined IDs supplied out-of-band.

> [!NOTE]
>
> Sellers that support caller-scope introspection attach an optional `authorization` object to each per-account entry in [`sync_accounts`](/dist/docs/3.3.0-beta.0/accounts/tasks/sync_accounts) and [`list_accounts`](/dist/docs/3.3.0-beta.0/accounts/tasks/list_accounts) responses — listing the tasks and request fields this caller is allowed to use on the account, plus any standard named scope (e.g., `attestation_verifier`). See [Caller authorization](/dist/docs/3.3.0-beta.0/accounts/overview#caller-authorization) for the full shape and semantics.

### Account-id namespaces (`require_operator_auth: true`)

Accounts are managed outside of AdCP. The advertiser creates an account on the seller's platform, grants the operator permission to manage it, and the buyer passes a seller-assigned `account_id`. The agent is not involved in account creation or billing setup — those are handled between the advertiser, operator, and seller directly.

**Typical sellers:** Social platforms, self-serve ad platforms — anywhere the advertiser already has an account.

**Upstream-managed workflow:**

1. Advertiser creates an account on the seller's platform (out-of-band)
2. Advertiser grants the operator permission to manage the account (out-of-band)
3. Agent calls `list_accounts` to discover available accounts
4. Human selects the correct account from the list
5. Agent passes `{ "account_id": "acc_acme_001" }` on every request (`get_products`, `create_media_buy`, etc.)

`list_accounts` is mandatory when the authenticated credential may access more than one account because the upstream owns the namespace. When the credential is bound to exactly one account, sellers SHOULD still expose `list_accounts` returning that singleton so SDKs can auto-select it and send explicit `{ "account_id": "..." }` on required-account calls. `sync_accounts` provisioning is out of scope for account-id namespaces in 3.0.x unless a future explicit capability declares it; if `sync_accounts` is exposed today, it is settings-update mode for an existing `account_id`.

**Seller-defined workflow:**

Some sellers use `account_id` without exposing an account discovery surface. In that pattern, the seller gives the buyer an account ID during onboarding or configuration, and the buyer passes that ID on account-scoped calls. Absence of `list_accounts` means there is no protocol namespace to discover; it does not mean the buyer should try to provision by natural key.

### Buyer-declared accounts (`require_operator_auth: false`)

The agent manages the buying relationship. It calls `sync_accounts` to tell the seller who's advertising, who's operating on the brand's behalf, and who's paying. The seller provisions accounts and responds with status — the account IDs are a byproduct of the declaration, not something the buyer needs to know upfront.

Buyer-declared-account sellers SHOULD also expose `list_accounts` as the recovery read for stateless buyers. Replaying `sync_accounts` is not a cold-start recovery mechanism when the buyer has lost the advertiser natural keys required to compose that replay. `list_accounts` returns those acknowledged natural-key fields so they round-trip into later calls; it does not make the seller-issued `account_id` mandatory.

**Typical sellers:** Traditional publishers, retail media networks, DSPs — anywhere the buying relationship is established programmatically.

`sync_accounts` is the declaration tool. Each entry is a set of flags that tells the seller what the buyer needs:

| Flag | What it tells the seller |
| - | - |
| `brand` (`domain` + optional `brand_id`/`countries[]`) | Which commercial advertiser entity is advertising; countries do not target delivery |
| `operator` | Who operates on the brand's behalf (agency, trading desk, or the brand itself) |
| `operator_unit` | Optional operator-owned business unit, seat, or platform account; stable `id` plus mutable display `name` |
| `currency` | Optional immutable account currency when the seller's advertiser object is currency-bound |
| `billing` | Who gets the invoice — `operator`, `agent`, or `advertiser` |
| `billing_entity` | Structured business entity details for the party responsible for payment — legal name, VAT ID, tax ID, address, contacts, and bank details. Used for formal B2B invoicing. Bank details are write-only (never echoed in responses). |
| `payment_terms` | Payment terms for this account (`net_15`, `net_30`, `net_45`, `net_60`, `net_90`, `prepay`). The seller must accept these terms or reject the account — terms are never silently remapped. |
| `sandbox` | Whether this is a sandbox (test) account — no real spend. Only used with buyer-declared accounts; account-id namespace sandboxes are pre-existing and discovered through `list_accounts` or supplied out-of-band. |

Every combination of flags that might require the seller to do something different — bill a different entity, set up a different rate card, create a sandbox — is a distinct declaration.

### Billing entity and invoice recipient

For markets that require structured invoicing data (e.g., EU B2B transactions requiring VAT IDs), the `billing_entity` on an account provides the default business entity details for whoever `billing` points to. This includes legal name, tax identifiers, postal address, billing contact, and bank details.

On individual media buys, an `invoice_recipient` can override the account default — useful when a specific campaign should be billed to a different party. When `invoice_recipient` differs from the account default and the account has `governance_agents`, the seller MUST include it in the `check_governance` request so the governance agent can approve or reject the billing redirect.

**Workflow:**

1. Agent calls `sync_accounts` with one or more declarations
2. Seller provisions or links accounts for each, responds with status:
   * `active` — ready to use
   * `pending_approval` — seller reviewing (human may need to visit `setup.url`)
   * `rejected` — seller declined the request
3. For subsequent requests, pass the account reference:
   * **Buyer-declared accounts** (`require_operator_auth: false`): pass the natural key `{ "brand": { "domain": "acme-corp.com" }, "operator": "pinnacle-media.com" }`
   * **Account-id namespaces** (`require_operator_auth: true`): pass `{ "account_id": "acc_acme_001" }` (discover via `list_accounts` for upstream-managed namespaces, or receive out-of-band for seller-defined namespaces)
   * **Sandbox (buyer-declared)**: pass the natural key with `sandbox: true` (declared via `sync_accounts`)
   * **Sandbox (account-id namespace)**: pass `{ "account_id": "test_acc_001" }` (pre-existing test account, discovered via `list_accounts` or supplied out-of-band)
4. When anything changes (billing model, new brand, new operator), call `sync_accounts` again
5. After a cold start, call `list_accounts` to recover previously acknowledged relationships when the seller exposes the recommended read surface

The agent may be directly responsible for billing when `billing` is `"agent"`. When `billing` is `"operator"` or `"advertiser"`, the agent facilitates but is not the invoiced party. The seller may require human approval before activating accounts.

### Natural key semantics

The tuple `(brand, operator, operator_unit.id, currency, timezone, sandbox)` identifies the advertiser object in the seller system. The nested `brand` carries `domain`, optional `brand_id`, and optional ISO country `countries[]`; these countries qualify commercial identity and never target delivery. `operator_unit` identifies the operator's own business unit, agency seat, or buying-platform account. Its stable `id` participates in identity, while its optional `name` is display metadata and may change without creating a new account. `currency` participates only when the seller's advertiser object is locked to one currency. `timezone` participates only when the seller requires the buyer to select an immutable account timezone during provisioning. Omit either qualifier when its capability mode does not apply.

This separates two account namespaces that must not be conflated: `operator_unit.id` is assigned by the buyer-side operator, while `account_id` is assigned by the AdCP seller/storefront. Both may be opaque values, but they identify different objects and are not interchangeable.

See [sync\_accounts task reference](/dist/docs/3.3.0-beta.0/accounts/tasks/sync_accounts) for the full request/response schema.

### Account status

| Status | Meaning | Next step |
| - | - | - |
| `active` | Ready to use | Place buys on this account |
| `pending_approval` | Seller reviewing | Human may need to visit `setup.url`. Poll `list_accounts` for updates. |
| `rejected` | Seller declined the request | Review rejection reason, adjust and re-sync, or contact seller |
| `payment_required` | Credit limit reached | Add funds or route spend to other accounts |
| `suspended` | Was active, now paused | Contact seller |
| `closed` | Was active, now terminated | — |

### Account scope

The agent requests accounts by natural key — `(brand, operator)`. The seller decides what granularity to assign. The `account_scope` field in the response tells the agent how the seller resolved the request:

| Scope | Meaning | Example |
| - | - | - |
| `operator` | One account for all brands under this operator | Agent sends (Pinnacle Media, Acme) and (Pinnacle Media, Nova) — seller maps both to the Pinnacle Media account |
| `brand` | One account for this brand regardless of operator | Agent sends (Acme, Pinnacle Media) and (Acme, Summit Agency) — seller maps both to the Acme account |
| `operator_brand` | Dedicated account for this operator+brand pair | Agent sends (Pinnacle Media, Acme) — seller creates a specific Acme-via-Pinnacle account |
| `agent` | Agent-scoped account with no brand- or operator-specific split | Agent sends any brand — seller maps the request to the standing agent-scoped account |

The agent does not choose the scope — the seller assigns it based on its own account policy. An agent requesting `(brand: {domain: "acme-corp.com"}, operator: "pinnacle-media.com")` might receive an operator-scoped account, a brand-scoped account, or a dedicated operator\_brand account depending on the seller.

When multiple natural keys resolve to the same scope, the `account_scope` explains why.

For buyer-declared accounts (`require_operator_auth: false`), use the complete natural key (`brand` + `operator` + optional `operator_unit`, fixed `currency`, buyer-selected `timezone`, and `sandbox`) on subsequent requests. A seller SHOULD also return an `account_id` from `sync_accounts`, MUST continue accepting the natural-key `AccountRef` for every account provisioned this way, and MUST return its natural-key fields from `list_accounts`. When the seller declares `account.stable_account_id: true`, buyers SHOULD send that `account_id` instead of the natural key. For account-id namespaces (`require_operator_auth: true`), discover seller/storefront account IDs via `list_accounts` for upstream-managed namespaces, or receive them out-of-band for seller-defined namespaces — including sandbox test accounts.

## Seller walkthrough: buyer-declared accounts

This walkthrough is for a seller that declares `require_operator_auth: false` (or omits it). It covers how the seller identifies the calling agent, where the account's operator comes from, and which error applies when.

Three identities show up on every request. Keep them apart:

| Identity | Where the seller gets it | Example |
| - | - | - |
| **Calling agent** | The transport: a verified request signature, or a credential the seller issued at onboarding | `https://agent.orbit-buying.example/mcp` |
| **Agent's operator** | The brand.json that lists the agent in `agents[]`, meaning whoever runs the agent | `orbit-buying.example` |
| **Account operator** | The request payload: `accounts[].operator` on `sync_accounts`, `account.operator` on later calls | `pinnacle-media.com` |

### 1. Onboard the agent

Before the agent's first account-scoped call, the seller records the agent in its onboarding system: the agent's canonical URL, how it authenticates, and its commercial state (see [Buyer-agent identity](#buyer-agent-identity)).

* **Signed requests.** The buyer gives the seller its agent URL during onboarding. From that URL, the seller runs [`brand_json_url` discovery](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url): capabilities, then `identity.brand_json_url`, then the brand.json `agents[]` entry, then `jwks_uri`. It indexes each `kid` in that JWKS against the agent's canonical URL. Rebuild the index whenever you refresh an agent's JWKS or brand.json, so a rotated `kid` still resolves and a delisted agent drops out. That cache is bounded by the revocation polling interval.
* **Seller-issued credential.** The seller issues an API key or OAuth client and maps it to the agent in the onboarding record. Before mapping a credential to an agent URL, confirm the counterparty controls that agent, for example with a signed onboarding request from it.

> [!NOTE]
>
> For now, the agent URL has to come from outside the request: onboarding, or a registry cache the seller trusts. A signed request carries only a `keyid`. Each signer picks its own `keyid` values, and nothing namespaces them, so a seller can't map a `keyid` it has never seen to an agent URL. AdCP 3.2 doesn't put the signer's agent URL on the wire. A discoverable signer agent URL is under discussion in [#7817](https://github.com/adcontextprotocol/adcp/issues/7817).

### 2. Identify the caller on each request

* **Signed request:** the verifier resolves the `keyid` against the agents it has onboarded. The `keyid` has to resolve under exactly one canonical `agents[].url` ([verifier checklist](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#verifier-checklist-requests), step 7). A `keyid` that isn't in any onboarded agent's JWKS, even after the one refetch step 7 allows, fails with `request_signature_key_unknown`. A `keyid` that more than one onboarded agent publishes also fails. The verifier never picks one, and never tries each agent's keys in turn. Agents that share a JWKS can't be told apart by signature alone, so use a seller-issued credential for them. On success, the authenticated caller is the canonical agent URL. It isn't the `keyid`, the domain, or the JWKS URI ([Agent identity](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#agent-identity)).
* **Unsigned request with a seller-issued credential:** the seller looks up the credential in its onboarding record, and the mapped agent is the caller.

Nothing in the request body identifies the caller.

### 3. Read the account operator from the request

The authenticated caller is the agent. The operator belongs to the account the agent names in the payload. One agent often acts for many operators. Here one buying agent, with one signing identity, declares accounts for two agencies:

```json theme={null}
{
  "idempotency_key": "sync-7f3c2a90-4d1e-4b8a-9c55-2e61f0a7b3d4",
  "accounts": [
    { "brand": { "domain": "nova-brands.com", "brand_id": "spark" }, "operator": "pinnacle-media.com", "billing": "agent" },
    { "brand": { "domain": "acme-corp.com" }, "operator": "summit-agency.jp", "billing": "agent" }
  ]
}
```

The seller takes `operator` from each entry, and later from `account.operator`. It doesn't substitute a value from the transport, such as the agent URL, the agent's brand.json domain, or an `operator` field an SDK attaches to the authenticated principal. If it did, both accounts would be attributed to `orbit-buying.example` instead of their agencies, and invoices would go to the wrong party. Two agencies buying the same brand through this agent would also share one account and see each other's buys and reporting.

Authentication still limits what the payload can reach. On calls after provisioning, each account the agent references has to be in that agent's authorized set ([Agent and account isolation](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#agent-and-account-isolation)). This matches [Tenant resolution](/dist/docs/3.3.0-beta.0/building/by-layer/L2/authentication#tenant-resolution): the authenticated principal decides which accounts are reachable, and the account reference only selects one. For each `sync_accounts` entry the seller also runs the [brand-operator check](#authorized-operators) and checks the agent's commercial state against the requested `billing`.

`sync_accounts` is how an account enters the calling agent's authorized set. The brand-operator check shows that the operator may represent the brand. It doesn't show that this agent acts for that operator. If the seller maps a declaration onto an account another agent already uses, the caller gets every object bound to that account. Decide that deliberately before joining an existing account, for example by keeping accounts per agent or confirming out of band.

### 4. Discover products with the natural key

Once `sync_accounts` returns `status: "active"`, the agent sends the same natural key on `get_products`:

```json theme={null}
{
  "buying_mode": "brief",
  "brief": "Q3 awareness for Spark in the US",
  "account": {
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com"
  }
}
```

When `account.required_for_products` is `true`, `get_products` needs this account. Otherwise the account is optional and selects account-specific pricing. Before `sync_accounts` succeeds, leave `account` off and send top-level `brand`: a natural key the seller hasn't provisioned returns `ACCOUNT_NOT_FOUND`, even on `get_products` ([Account references before provisioning](/dist/docs/3.3.0-beta.0/accounts/overview#account-references-before-provisioning)). A seller can instead provision lazily on the first provisioning task, such as `create_media_buy`, but only when billing and other required settings are clear from its capabilities or onboarding defaults. Discovery tasks such as `get_products` never provision, though a lazy-provisioning seller may answer them for the account it would create. Such a seller still has to accept the natural key and expose `list_accounts`. If it needs buyer input before the account is usable, it has to expose `sync_accounts` (see [`require_operator_auth`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities#account)).

### 5. Which error applies when

Authentication resolves first. Signature failures are transport errors, returned as `401` with `WWW-Authenticate: Signature error="<code>"` ([transport error taxonomy](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#transport-error-taxonomy)). Everything after that is an AdCP error: task-level `errors[]`, or on `sync_accounts` the per-entry `accounts[].errors` with `action: "failed"`.

| Request | Result |
| - | - |
| `Signature` or `Signature-Input` present without the other, or malformed | `request_signature_header_malformed`. The seller doesn't fall back to other credentials. |
| Signed, and fails the verifier checklist (unknown `keyid`, bad window, bad digest, and so on) | The checklist's code, such as `request_signature_key_unknown` (also `request_target_uri_malformed` or `request_body_malformed`). The seller doesn't fall back to other credentials. Exception: for an operation in `warn_for` (or a method in `protocol_methods_warn_for`), a well-formed signature that fails is logged, establishes no signer identity, and the request continues only if another credential authenticates it. |
| Unsigned. No credential the seller accepts for this operation. The resolved operation (the MCP tool name, or the A2A invocation `skill`; see [Operation resolution over A2A](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#operation-resolution-over-a2a)) is in `required_for`, or its JSON-RPC method is in `protocol_methods_required_for` | `request_signature_required`. This applies even if an unrecognized bearer token was sent. |
| Unsigned, the seller supports request signing, and the payload carries a webhook `authentication` block (`accounts[].notification_configs[].authentication`, `push_notification_config.authentication`) | `request_signature_required`, even with a valid credential ([webhook callbacks](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#webhook-callbacks)) |
| Unsigned, with a credential the seller accepts for this operation | Authenticated through that credential. This holds for `required_for` operations too, unless the payload carries a webhook `authentication` block (previous row), or the seller configured that authenticator to reject the operation ([composition with fallback authenticators](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#composition-with-fallback-authenticators)). |
| Unsigned, the operation needs authentication and doesn't require a signature, and no credentials | `AUTH_MISSING` |
| Unsigned, the operation doesn't require a signature, and the credential was presented but rejected | `AUTH_INVALID` |
| Authenticated agent is suspended or blocked | `AGENT_SUSPENDED` or `AGENT_BLOCKED` |
| Requested `billing` not available | `BILLING_NOT_SUPPORTED`, or `BILLING_NOT_PERMITTED_FOR_AGENT` for a passthrough-only agent |
| Natural key doesn't resolve (not yet provisioned, and the seller isn't lazily provisioning it on this task or answering discovery for the account it would create), or resolves to an account outside the agent's authorized set | `ACCOUNT_NOT_FOUND`, including when `account` is optional on the task. The response is the same in both cases, so it doesn't reveal whether the account exists. |
| Former natural key retired by an account rekey, and the caller is authorized for the current account | `ACCOUNT_MOVED`, with `details.current_account` |
| Natural key matches more than one account in the agent's authorized set | `ACCOUNT_AMBIGUOUS` |
| An account in the authorized set is found, but its state blocks the call | `ACCOUNT_SETUP_REQUIRED`, `ACCOUNT_SUSPENDED`, or `ACCOUNT_PAYMENT_REQUIRED` ([Error codes](#error-codes)) |

Resolve and disambiguate only within the agent's authorized set. To the caller, an account outside that set doesn't exist.

## Error codes

| Code | When returned | Resolution |
| - | - | - |
| `ACCOUNT_REQUIRED` | Multiple accounts; seller can't determine which | Pass `account_id` in the account reference |
| `ACCOUNT_NOT_FOUND` | Account reference doesn't resolve: an `account_id` that doesn't exist, a natural key not yet provisioned, or an account the agent can't access | Natural key: provision through `sync_accounts`, or omit `account` for public discovery. `account_id`: verify through `list_accounts` |
| `ACCOUNT_SETUP_REQUIRED` | Natural key resolved but account needs setup | Check `details.setup` for URL/message |
| `ACCOUNT_AMBIGUOUS` | Natural key resolves to multiple accounts | Pass `account_id` or more specific natural key |
| `ACCOUNT_PAYMENT_REQUIRED` | Credit limit reached or funds depleted | Add funds, route to another account |
| `ACCOUNT_SUSPENDED` | Account not in good standing | Contact seller |
| `BRAND_REQUIRED` | Billable operation without brand reference | Include `brand` in request |

When the seller returns `ACCOUNT_REQUIRED`, it includes the available accounts:

```json theme={null}
{
  "errors": [{
    "code": "ACCOUNT_REQUIRED",
    "message": "Multiple accounts available. Please specify account_id in the account reference.",
    "details": {
      "available_accounts": [
        { "account_id": "acc_acme_001", "name": "Acme Corp" },
        { "account_id": "acc_pinnacle", "name": "Pinnacle Media" }
      ]
    }
  }]
}
```

## Design notes

### sync\_accounts and seller record systems

When an agent declares `(brand: {domain: "acme-corp.com"}, operator: "pinnacle-media.com")`, the seller looks up or creates records in its own system — CRM, OMS, ad server, or billing platform.

`sync_accounts` is the buyer-side interface to the seller's record system. The seller may:

* Map the natural key to an existing account and return `status: "active"`
* Create a new record and return it immediately (`status: "active"`)
* Create a placeholder pending human review (`status: "pending_approval"`)
* Decline the request entirely (`status: "rejected"`)

`list_accounts` returns all records the seller has mapped for this agent — including pending and rejected entries. The agent uses `list_accounts` to see the full state of its portfolio with this seller, not just active accounts.

### Accounts and insertion orders

An account represents a standing relationship — who gets billed, what rates apply, what credit is available. It is not a campaign or an insertion order.

Insertion orders and campaign flights are modeled as media buys via `create_media_buy`. The account determines *billing terms*; the media buy determines *what runs and when*. A single account can have many media buys over its lifetime.

### Operator revocation and caching

If a brand removes an operator from `authorized_operators`, existing active accounts are not automatically deactivated. Revocation is eventual, not immediate — similar to how `ads.txt` changes propagate on the supply side.

Sellers SHOULD respect standard HTTP caching headers on `brand.json` and re-validate periodically. A reasonable cache TTL is 24 hours.

### Brand identity for SMBs

Domain-based identity via `/.well-known/brand.json` works for organizations of any size — it's a static JSON file that can be hosted on any web server.

For organizations that cannot host files on their domain, the `authoritative_location` field in `brand.json` allows the house domain to redirect to a hosted location:

```json theme={null}
{
  "house": {
    "domain": "local-bakery.com"
  },
  "authoritative_location": "https://registry.agenticadvertising.org/brands/local-bakery.com"
}
```


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