> ## 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), explicit vs implicit account models, 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` ([brand.json](/dist/docs/3.0.0-rc.2/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`), resolved via `/.well-known/brand.json`. Single-brand houses use the domain alone (no `brand_id`).

**Account** — A billing relationship between a buyer and seller. Determines rate card, payment terms, credit limit, and who receives invoices. Every billable operation requires an account reference — a seller-assigned `account_id` (explicit accounts) or a natural key (`brand`, `operator`) (implicit accounts). Sandbox accounts follow the same model — explicit sandboxes use `account_id`, implicit sandboxes use the natural key with `sandbox: true`.

**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.0.0-rc.2/accounts/overview) for the full commercial model and [sync\_accounts](/dist/docs/3.0.0-rc.2/accounts/tasks/sync_accounts) for the task reference.

## What sellers declare

Sellers configure the `account` section of [`get_adcp_capabilities`](/dist/docs/3.0.0-rc.2/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 |

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

This single field determines both the authentication model and how accounts are referenced:

When `false` (default) — **implicit 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.

When `true` — **explicit accounts**: 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. The buyer discovers accounts via `list_accounts` and passes a seller-assigned `account_id`.

For sandbox, the path follows the account model: explicit accounts (`require_operator_auth: true`) discover pre-existing test accounts via `list_accounts`; implicit accounts declare sandbox via `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.0.0-rc.2/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"]
  }
}
```

These two fields combine into three common patterns.

## Seller patterns

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

| Platform type                              | `require_operator_auth` | `supported_billing`                       |
| ------------------------------------------ | ----------------------- | ----------------------------------------- |
| [Social / walled garden](#social-platform) | `true`                  | `["operator"]`                            |
| [Direct publisher](#direct-publisher)      | `false`                 | `["operator"]` or `["operator", "agent"]` |
| [DSP / programmatic](#dsp--programmatic)   | `false`                 | `["agent"]`                               |

### Social platform

The operator already has an account on the platform — an ad account, a business manager, a self-serve dashboard. The agent obtains the operator's credentials (via OAuth or API key) and opens a per-operator session. The platform bills the operator directly.

**Capabilities:**

```json theme={null}
{
  "account": {
    "require_operator_auth": true,
    "supported_billing": ["operator"],
    "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 `sync_accounts` to set up each brand for this operator
3. Wait for account status `active` (poll `list_accounts` if `pending_approval`)
4. Call `get_products` / `create_media_buy` with the operator's session and `account` reference

**sync\_accounts request:**

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

Seller checks `nova-brands.com/.well-known/brand.json`, finds Pinnacle Media in `authorized_operators`, and fast-tracks provisioning:

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

**Key point:** The operator's credential — not the agent's — authorizes all calls in that session. Brand.json verification is secondary to the credential.

### 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"]
  }
}
```

**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"]
  }
}
```

**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

Verification is a trust signal, not a gate. Finding the operator in `brand.json` lets the seller fast-track provisioning. If the operator isn't listed, the seller can still approve through its own review process.

**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. It does not model internal agency hierarchies.

## 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 which model applies — and the model determines the buyer's entire integration path.

### Explicit accounts (`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 agent discovers the account via `list_accounts`. The agent is not involved in authentication or billing — those are handled between the advertiser and seller directly.

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

**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.)

The agent doesn't set up accounts, negotiate billing, or manage authentication with the seller. It just discovers what already exists and lets the human choose.

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

**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`) | Which brand is advertising                                                                                                                                                                 |
| `operator`                               | Who operates on the brand's behalf (agency, trading desk, or the brand itself)                                                                                                             |
| `billing`                                | Who gets the invoice — `operator` or `agent`                                                                                                                                               |
| `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 implicit accounts; explicit sandbox accounts are pre-existing.                                                    |

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.

**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:
   * **Implicit accounts** (`require_operator_auth: false`): pass the natural key `{ "brand": { "domain": "acme-corp.com" }, "operator": "pinnacle-media.com" }`
   * **Explicit accounts** (`require_operator_auth: true`): pass `{ "account_id": "acc_acme_001" }` (discover via `list_accounts`)
   * **Sandbox (implicit)**: pass the natural key with `sandbox: true` (declared via `sync_accounts`)
   * **Sandbox (explicit)**: pass `{ "account_id": "test_acc_001" }` (pre-existing test account, discovered via `list_accounts`)
4. When anything changes (billing model, new brand, new operator), call `sync_accounts` again

The agent may be involved in billing (when `billing` is `"agent"` or `"operator"`). The seller may require human approval before activating accounts.

### Natural key semantics

The tuple `(brand, operator, sandbox)` uniquely identifies an account relationship. The `brand` is a nested object with `domain` and optional `brand_id`. `operator` is always required — when the brand operates directly, set `operator` to the brand's domain. `sandbox` defaults to `false` when omitted. For example, `{brand: {domain: "acme-corp.com"}, operator: "acme-corp.com"}` (brand buying direct) is a different account from `{brand: {domain: "acme-corp.com"}, operator: "pinnacle-media.com"}` (brand via agency). Adding `sandbox: true` references the sandbox account for the same pair.

See [sync\_accounts task reference](/dist/docs/3.0.0-rc.2/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`          | The agent's default account                       | Agent sends any brand — seller routes to the standing agent 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.

`sync_accounts` does not return `account_id` — the seller manages account identifiers internally. For explicit accounts (`require_operator_auth: true`), discover account IDs via `list_accounts` — including sandbox test accounts. For implicit accounts (`require_operator_auth: false`), use natural keys (`brand` + `operator`) on subsequent requests — adding `sandbox: true` for sandbox accounts.

## 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_id` doesn't exist or agent lacks access | Check account reference, re-run `sync_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  |
| `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"
}
```
