Skip to main content
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.
Experimental. Account requests (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 results) are an experimental AdCP 3.3 surface (#8280, #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.
Request schema: account/request-account-request.json Response schema: 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 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 advertises account.requests.supported: true and lists account.requests in experimental_features. Otherwise, use 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, and the brand-to-account mapping stays on the buyer side.

Lifecycle

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

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.

Changing an account

Send the account_id and only the attributes to change. The changeable attributes are those #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.
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 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, READ_ONLY_SCOPE, or 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 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.
A day later, sales ops finds no relationship and hands the next step to the buyer:
Once the human registers, the request resolves:

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, 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):

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 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, CONFLICT, or 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_ids 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, 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.
  • 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 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. 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 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.
  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 or sync_agent_notification_configs, or poll 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 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