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

# request_account

> request_account is the single account write: open an account (the seller matches or creates it) or change one by account_id, possibly after human review on either side.

`request_account` is the one way to write an account. Send a brand and operator to open one: the seller looks for an existing account first, creates one if it should, or tells the buyer what a human has to do before it can. Send an `account_id` and attributes to change one. Every write is a request with the same lifecycle, read back through [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts).

> [!NOTE]
>
> **Experimental.** Account requests ([`request_account`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/request_account), `include_requests` / `request_ids` on `list_accounts`, the `account_request.changed` webhook, the `account.requests` capability block, and the `request_id`, `resolution`, `setup.waiting_on`, and `setup.kind` fields on [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) results) are an experimental AdCP 3.3 surface ([#8280](https://github.com/adcontextprotocol/adcp/issues/8280), [#8543](https://github.com/adcontextprotocol/adcp/issues/8543)). They may change between 3.x releases with at least 6 weeks' notice. Sellers implementing any part of it MUST declare `account.requests` in `experimental_features`. See [experimental status](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/reference/experimental-status.md).

**Request schema:** [`account/request-account-request.json`](https://adcontextprotocol.org/schemas/3.3.0-beta.1/account/request-account-request.json)
**Response schema:** [`account/request-account-response.json`](https://adcontextprotocol.org/schemas/3.3.0-beta.1/account/request-account-response.json)

## Why a request, not an account

Getting an account usually involves a person. When a storefront receives "WPP Media FR for Renault," someone in sales ops checks the CRM, where the record is named something like "Renault SAS – France," not keyed on a domain. The review can end four ways: the seller already has a relationship and links it, creates a new account, asks a human on the buyer side to sign up first, or declines.

[`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) models this as a placeholder account in `pending_approval`. A placeholder can't become an existing account without changing its `account_id`, and a rejected placeholder keeps holding the brand and operator. A request is its own object instead: no account exists until the request resolves, so a match never changes an ID and a rejection never blocks a retry.

## When to call

Call it only when [`get_adcp_capabilities`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/get_adcp_capabilities) advertises `account.requests.supported: true` and lists `account.requests` in `experimental_features`. Otherwise, use [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts), which stays stable and available in 3.3. `account.requests.changeable_attributes` lists the attributes a seller accepts in change requests; absent or empty means it supports opening accounts only.

* **Agent-declared sellers** (storefronts, inventory sources, house seats): `request_account` is the way to get an account.
* **Sellers that own their account namespace** (platforms where the credential's access decides which accounts exist): `request_account` is optional. When offered, it matches among accounts the credential already reaches, or returns a buyer action such as creating an ad account or asking the brand to grant access. Sellers that don't offer it rely on [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts), and the brand-to-account mapping stays on the buyer side.

## Lifecycle

| `state` | Carries | What the buyer does |
| - | - | - |
| `in_review` | `pending_action` | `waiting_on: seller`: wait for `account_request.changed` or poll [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) with `request_ids`. `waiting_on: buyer`: show `pending_action.url` to a human, then keep waiting. |
| `resolved` | `account_id`, `resolution` (`matched`, `created`, or `updated`), and for a change the updated `account` | Store `account_id` and send it on every account-scoped task. |
| `ambiguous` | `candidates[]` | A human picks one; call `request_account` with that `account_id` and the open `request_id`, and the request resolves as `matched`. |
| `rejected` | `errors[]` | Tell a human. A new request for the same key opens a new request. |

`in_review` and `ambiguous` are open; `resolved` and `rejected` are terminal for that request. A seller that approves automatically resolves in the same response. A seller that reviews starts `in_review` with `waiting_on: seller`.

`pending_action.kind` says what is needed: `review`, `sign_up`, `accept_terms`, `credit_application`, `add_payment`, `grant`, or `connect_credentials`. The list is extensible; for an unknown kind, fall back to `message` and `url`.

## Request

| Field | Required | Description |
| - | - | - |
| `idempotency_key` | Yes | Makes retries of this call safe. A replayed key returns the original request; see [deduplication](#deduplication). |
| `brand` | To open | BrandKey: `domain`, optional `brand_id`, optional `countries[]`. With `account_id`, send it only to change countries; `domain` and `brand_id` must equal the account's. |
| `operator` | To open | Domain of the entity operating for the brand; the brand's own domain when it buys direct. |
| `operator_unit` | No | Operator-owned unit or seat. `id` is an account attribute; `name` is display metadata. |
| `currency` | No | ISO 4217 currency for a currency-bound advertiser object. An account attribute set when the account opens. |
| `timezone` | Conditional | Buyer-selected timezone, as in [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts). An account attribute set when the account opens. |
| `sandbox` | No | Request a sandbox account. An account attribute. |
| `billing` | No | Requested invoiced party: `operator`, `agent`, or `advertiser`. Accepted exactly or rejected. |
| `billing_entity` | No | Requested business entity for the invoiced party. Bank details are write-only. |
| `payment_terms` | No | Requested terms. Accepted or rejected, never silently remapped. |
| `request_id` | No | Continue one of your own open requests: with `account_id` and no change fields, pick a candidate from an `ambiguous` request or name the account a human created after a `sign_up` action (the request resolves as `matched`). Change requests are never edited in place; send a new one, which supersedes the open one. |
| `account_id` | To change or select | Change this account: every other field present is a requested change. With no other fields, select it: a candidate from an `ambiguous` result, or an account the credential doesn't reach yet. |
| `revision` | For money changes | With `account_id`: the account revision a change is based on. **Required** when the change touches `billing`, `billing_entity`, or `payment_terms`, since a lost update there costs money; optional otherwise. A stale revision fails with [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict) and changes nothing. |

Send either `account_id` (to change or select), or `brand` and `operator` (to open). A request with `account_id` and `operator`, or with neither, fails with [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request).

```json theme={null}
{
  "adcp_version": "3.3",
  "idempotency_key": "3f1d2a8c-6b0e-4c7a-9d51-8e2f4a6b9c10",
  "brand": { "domain": "renault.fr", "countries": ["FR"] },
  "operator": "wppmedia.fr",
  "currency": "EUR",
  "billing": "operator",
  "billing_entity": { "legal_name": "WPP Media France SAS" }
}
```

## Changing an account

Send the `account_id` and only the attributes to change. The changeable attributes are those [#8474](https://github.com/adcontextprotocol/adcp/pull/8474) defines: the brand's `countries`, `operator_unit`, `billing`, `billing_entity`, and `payment_terms`. `currency` and `timezone` are set when the account opens, and a seller MAY refuse to change them; `sandbox` never changes.

```json theme={null}
{
  "adcp_version": "3.3",
  "idempotency_key": "9d4e1b7a-2c6f-4a83-b5e0-7f1a3c8d2b64",
  "account_id": "acc_nova_pinnacle",
  "revision": 7,
  "brand": { "domain": "nova-athletics.example", "countries": ["BE", "DE", "NL"] }
}
```

The seller validates a changed attribute as it does when the account opens, including authorization such as the brand-operator check. Then one of:

* **It applies the change:** `state: resolved`, `resolution: updated`, and `account` carrying the updated attributes and new `revision`.
* **It reviews the change first:** `state: in_review` with a pending action. The account keeps its current attributes until the request resolves.
* **It refuses the change:** `state: rejected` with the reason. The account is unchanged.

The account keeps its `account_id`, media buys, and reporting history either way. Changes never alter the account's identity, so there is nothing to retire.

**Who may change an account.** The seller first resolves `account_id` against the caller's own access and answers an unreachable account exactly as an unknown one, before validating anything else in the body, so a probe learns nothing. A change then requires `request_account` in the caller's [grant](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/overview.md#caller-authorization) `allowed_tasks` for that account, with `read_only: false`. When the grant lists `request_account` under `field_scopes`, every changed field, including `billing`, `billing_entity`, and `payment_terms`, must be in that allowlist, as `field_scopes` already works. Otherwise the request fails with [`SCOPE_INSUFFICIENT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-scope-insufficient), [`READ_ONLY_SCOPE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-read-only-scope), or [`FIELD_NOT_PERMITTED`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-field-not-permitted).

**Concurrent changes.** A change that waits `in_review` re-checks `revision` when it resolves. If the account changed in the meantime, the request is rejected with [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict) instead of being applied on top. A new change request for the same account from the same caller **supersedes** that caller's open change request: the new request gets its own `request_id` and must carry every change still wanted, and the open one moves to `rejected` with `CONFLICT` and `details.superseded_by`. Change requests are never edited in place, and another caller's requests are never affected. Subscription writes (webhook and reporting configuration) don't advance the account's `revision`, so they never fail a pending change.

**One account per call.** `request_account` has no batch form. A buyer opening many accounts makes one call per account, which keeps each request's lifecycle separate.

## Response

A completed response always carries the request, whatever its state. `in_review` and `rejected` are normal outcomes, not task failures; `status: "failed"` is reserved for calls the seller couldn't process.

```json theme={null}
{
  "status": "completed",
  "request": {
    "request_id": "areq_01K4F8M2Q7",
    "state": "in_review",
    "requested": {
      "brand": { "domain": "renault.fr", "countries": ["FR"] },
      "operator": "wppmedia.fr",
      "currency": "EUR",
      "billing": "operator"
    },
    "pending_action": {
      "waiting_on": "seller",
      "kind": "review",
      "message": "Checking for an existing Renault France relationship."
    },
    "created_at": "2026-10-18T09:00:00Z",
    "updated_at": "2026-10-18T09:00:00Z"
  }
}
```

A day later, sales ops finds no relationship and hands the next step to the buyer:

```json theme={null}
{
  "request_id": "areq_01K4F8M2Q7",
  "state": "in_review",
  "pending_action": {
    "waiting_on": "buyer",
    "kind": "sign_up",
    "url": "https://advertisers.streamhaus.example/register?ref=areq_01K4F8M2Q7",
    "message": "No existing relationship found. Register WPP Media France as an advertiser.",
    "expires_at": "2026-11-15T00:00:00Z"
  }
}
```

Once the human registers, the request resolves:

```json theme={null}
{
  "request_id": "areq_01K4F8M2Q7",
  "state": "resolved",
  "account_id": "acc_renault_fr_wpp",
  "resolution": "created"
}
```

## Deduplication

The protocol requires only that retries are safe. Replaying an `idempotency_key` returns the original request, and replaying it with a different body returns [`IDEMPOTENCY_CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-idempotency-conflict), as on every AdCP mutation. A new key is a new request.

What a seller does with separate requests for the same advertiser is its choice. This guidance is non-normative:

* **Per calling agent.** Requests are scoped to the authenticated caller. Two agents asking for the same advertiser get two requests, and both may resolve to the same account (`matched`), each with its own grant.
* **Match on brand and operator.** Treat a request for the same brand (`domain` and `brand_id`) and `operator` as the same advertiser. `brand.countries`, `currency`, `timezone`, `sandbox`, and `operator_unit` are attributes of the account: use them as filters that narrow which account fits, not as identity.
* **Terms are not identity.** `billing`, `billing_entity`, and `payment_terms` are requested terms. One advertiser maps to one account whoever is invoiced.

A seller that does deduplicate typically answers a repeated *open* request as follows (change requests supersede instead; see [Changing an account](#changing-an-account)):

| Existing request | Result |
| - | - |
| `in_review` or `ambiguous` | The same request. Changed terms replace the requested terms; the seller MAY restart review. |
| `resolved` | The same request and account, with the account's actual terms. Changing terms is a change request (`request_account` with the `account_id`), never a silent re-review. |
| `rejected` | A new request. The seller MAY reject it again at once, for example during a cool-down. |
| `resolved`, account since closed | A new request, which may create a new account. |

## Seller rules

* **No provisional account IDs.** Never issue an `account_id` that might later change; while a request might match an existing account, issue none until it resolves. Open requests are visible through [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) with `include_requests`, in `account_requests[]`, never as accounts.
* **No existence oracle.** `matched` and `candidates[]` disclose only accounts the calling agent is, or is now, granted. When the only match is an account another operator or agent holds, every state, pending-action kind, and error the caller sees MUST be one the seller could also return when nothing matches: for example, `review` followed by `sign_up`, a new account, or a rejection with a generic reason.
* **Check reachability first.** Resolve `account_id` against the caller's access before any other validation, so [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request), [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict), or [`UNSUPPORTED_FEATURE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-unsupported-feature) never reveal that an unreachable account exists.
* **Never echo write-only fields.** `requested` never includes `billing_entity` bank details or other write-only fields.
* **Unreachable `account_id`s look like unknown ones.** For an `account_id` the caller can't reach, answer the same way whether or not the account exists: always `rejected` with [`ACCOUNT_NOT_FOUND`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-account-not-found), or, for sellers that support access requests, always `in_review` with `waiting_on: buyer` and `kind: grant`.
* **Action URLs aren't bearer links.** Possession of `pending_action.url`, or of a `request_id` in it, MUST NOT by itself be enough to complete the action; the human authenticates with the seller first.
* **Grant the caller on resolution.** A resolved account appears in the caller's `list_accounts` with its [caller authorization](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/overview.md#caller-authorization).
* **Check authority while reviewing.** Verify the operator against the brand's `authorized_operators`, and, where supported, the operator's grant to the calling agent. A missing grant is `waiting_on: buyer` with `kind: grant`, not a bare error.
* **Expire buyer actions.** A request still waiting on the buyer at `pending_action.expires_at` moves to `rejected` with an error explaining the expiry.
* **Fire `account_request.changed` on every seller-driven transition,** after the new state is readable through `list_accounts`, when the capability declares `event_type`. Sellers that don't fire it leave buyers to poll.
* **Fold in the agent application.** If the calling agent's own relationship with the seller is not yet active, the seller MAY review it together with its first account request, so the buyer makes one call and the seller one review.

## Relation to `sync_accounts`

[`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts) stays stable and available in 3.3; its formal deprecation follows `account.requests` leaving experimental status. A seller offering both keeps one internal request record behind them: `sync_accounts` provisioning rows are a projection of it. A `sync_accounts` settings update that lands while a `request_account` change is `in_review` advances the account's revision, so that change fails its revision re-check and is rejected with [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict).

| Request state | `sync_accounts` row |
| - | - |
| `in_review` | `status: pending_approval`, `setup` with `waiting_on` and `kind`, `request_id`. `account_id` only when it can never change. |
| `resolved` / `created` | `action: created` (then `unchanged` on repeats), `account_id`, `resolution: created` |
| `resolved` / `matched` | `action: unchanged`, `account_id`, `resolution: matched` |
| `ambiguous` | `action: failed`, [`ACCOUNT_AMBIGUOUS`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-account-ambiguous) with `error.details.candidates[]` |
| `rejected` | `status: rejected` with the reason in `warnings`, as `sync_accounts` already reports a declined account. `action: failed` with `errors[]` stays reserved for entries the seller couldn't process. |

**Buyers that predate account requests.** They ignore `request_id`, `resolution`, and the new `setup` fields, and learn the outcome the way they do today: by replaying `sync_accounts` with the same natural key, from the one-shot `push_notification_config`, or from `account.status_changed`. When a request resolves, the seller activates the row's account-level `notification_configs` first and then fires `account.status_changed` from `pending_approval` with the resolved `account_id`. The one visible difference is that a request-mode seller's pending rows don't appear in [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) until they resolve.

**Placeholder accounts.** A seller never issues an `account_id` it might later change. While a request is pending, the buyer tracks `request_id`. A seller that creates the account at approval and keeps the placeholder's ID, so it never changes, MAY issue it early; any seller that might match the request to an existing account does not.

## Buyer handling

1. If the seller advertises `account.requests`, call `request_account` to open or change accounts. Otherwise use [`sync_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/sync_accounts).
2. Store `request_id` against (seller, key).
3. While the request is open, if the seller declares `account.requests.event_type`, register for `account_request.changed` through [`sync_principal`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/sync_principal) or [`sync_agent_notification_configs`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/protocol/sync_agent_notification_configs), or poll [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) with `request_ids` and backoff.
4. Until it resolves, call discovery tasks without `account`, which returns the seller's public view.
5. After a cold start, [`list_accounts`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/accounts/tasks/list_accounts) with `include_requests: true` recovers accounts and open requests. Requests page separately from accounts: follow `account_requests_pagination` with `requests_pagination` until `has_more` is false.

## Errors

| Code | When |
| - | - |
| [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request) | `account_id` with `operator`, or neither `account_id` nor `brand` + `operator`; a change that names a different brand `domain` or `brand_id`. |
| [`CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-conflict) | A change's `revision` is stale, at the call or when an `in_review` change resolves; or the request was superseded by a newer change request from the same caller (`details.superseded_by`). |
| [`SCOPE_INSUFFICIENT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-scope-insufficient) / [`READ_ONLY_SCOPE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-read-only-scope) / [`FIELD_NOT_PERMITTED`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-field-not-permitted) | The caller's grant on the account doesn't permit the change, or doesn't cover a money field. |
| [`INVALID_REQUEST`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-invalid-request) (money change) | A change to `billing`, `billing_entity`, or `payment_terms` without `revision`. |
| [`BILLING_NOT_SUPPORTED`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-billing-not-supported) | The requested `billing` isn't in the seller's `supported_billing`. Sellers MAY instead record the request and reject it. |
| [`IDEMPOTENCY_CONFLICT`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-idempotency-conflict) | The `idempotency_key` was replayed with a different body. |
| [`UNSUPPORTED_FEATURE`](https://docs.adcontextprotocol.org/dist/docs/3.3.0-beta.1/building/verification/compliance-catalog#error-code-unsupported-feature) | The seller doesn't support account requests, or refuses to change `currency`, `timezone`, or `sandbox` on an existing account. |


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