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 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 for the full commercial model and sync_accounts for the task reference.
What sellers declare
Sellers configure theaccount section of get_adcp_capabilities:
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.
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.
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 determines the authentication model and the account reference shape:
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 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.
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. This only applies to operator-billed accounts.
Example capabilities:
advertiser billing declare it explicitly:
Seller patterns
Which kind of platform are you buying from? That determines the account setup pattern.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 throughlist_accounts, and uses the returned account_id values. The platform bills the operator directly.
Capabilities:
- Call
get_adcp_capabilities— seerequire_operator_auth: trueandauthorization_endpoint - 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. Calllist_accountsto discover accounts visible to that credential - Human or policy selects the correct account from the list
- Call
get_products/create_media_buywith the operator’s session and{ "account_id": "..." }
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 viasync_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:
- Call
get_adcp_capabilities— seerequire_operator_authabsent (defaults tofalse) - Call
sync_accountsfor each brand/operator pair - Wait for account status
active— may require human to complete credit/legal atsetup.url - Call
get_productswithaccountreference - Call
create_media_buywithaccountreference
(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_accountswith the same natural key — the seller returns the updated status - Receives a webhook notification if
push_notification_configwas provided in the request
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:
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:- Call
get_adcp_capabilities— seesupported_billing: ["agent"] - Call
sync_accountsfor each brand/operator pair withbilling: "agent" - Accounts are active immediately — no human approval needed
- Call
get_products/create_media_buywithaccountreference
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.
Verification flow
- Resolve
{brand.domain}/.well-known/brand.json - Check
authorized_operatorsfor matchingdomainwith the brand inbrands - If found → proceed (account may still need credit/legal approval)
- If not found → reject the account (
action: "failed") or returnpending_approvalfor manual review
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.
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:
Both layers MUST pass. A signed request from an onboarded agent for an unauthorized operator gets rejected 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 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 — 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:
- Runtime billing gate. A passthrough-only agent that submits
billing: "agent"orbilling: "advertiser"is rejected withBILLING_NOT_PERMITTED_FOR_AGENTand anerror.details.suggested_billingofoperator. See Billing and Account Setup for the recovery contract. - 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 thesync_accountsrequest 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 maintainbuyer_id/seat_idrows for OpenRTB DSPs); smaller publishers MAY collapse to seller-wide defaults until they have receivables ops that distinguish per-agent terms.
Account references
Every account-scoped operation accepts anaccount 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.
Sellers that support caller-scope introspection attach an optional
authorization object to each per-account entry in sync_accounts and 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 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:
- Advertiser creates an account on the seller’s platform (out-of-band)
- Advertiser grants the operator permission to manage the account (out-of-band)
- Agent calls
list_accountsto discover available accounts - Human selects the correct account from the list
- 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:
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), thebilling_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:
- Agent calls
sync_accountswith one or more declarations - Seller provisions or links accounts for each, responds with status:
active— ready to usepending_approval— seller reviewing (human may need to visitsetup.url)rejected— seller declined the request
- 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 vialist_accountsfor upstream-managed namespaces, or receive out-of-band for seller-defined namespaces) - Sandbox (buyer-declared): pass the natural key with
sandbox: true(declared viasync_accounts) - Sandbox (account-id namespace): pass
{ "account_id": "test_acc_001" }(pre-existing test account, discovered vialist_accountsor supplied out-of-band)
- Buyer-declared accounts (
- When anything changes (billing model, new brand, new operator), call
sync_accountsagain - After a cold start, call
list_accountsto recover previously acknowledged relationships when the seller exposes the recommended read surface
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 for the full request/response schema.
Account status
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:
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 may also return an account_id from sync_accounts as its internal handle, but the seller MUST continue accepting the natural-key AccountRef for every account provisioned this way and MUST return its natural-key fields from list_accounts. 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 declaresrequire_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:
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).- Signed requests. The buyer gives the seller its agent URL during onboarding. From that URL, the seller runs
brand_json_urldiscovery: capabilities, thenidentity.brand_json_url, then the brand.jsonagents[]entry, thenjwks_uri. It indexes eachkidin that JWKS against the agent’s canonical URL. Rebuild the index whenever you refresh an agent’s JWKS or brand.json, so a rotatedkidstill 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.
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.2. Identify the caller on each request
- Signed request: the verifier resolves the
keyidagainst the agents it has onboarded. Thekeyidhas to resolve under exactly one canonicalagents[].url(verifier checklist, step 7). Akeyidthat isn’t in any onboarded agent’s JWKS, even after the one refetch step 7 allows, fails withrequest_signature_key_unknown. Akeyidthat 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 thekeyid, the domain, or the JWKS URI (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.
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: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). This matches 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 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
Oncesync_accounts returns status: "active", the agent sends the same natural key on get_products:
account.required_for_products is true, get_products needs this account. Otherwise the account is optional and selects account-specific pricing. A seller can instead provision lazily on the first account-scoped request, but only when billing and other required settings are clear from its capabilities or onboarding defaults. 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).
5. Which error applies when
Authentication resolves first. Signature failures are transport errors, returned as401 with WWW-Authenticate: Signature error="<code>" (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".
Resolve and disambiguate only within the agent’s authorized set. To the caller, an account outside that set doesn’t exist.
Error codes
When the seller returns
ACCOUNT_REQUIRED, it includes the available accounts:
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 viacreate_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 fromauthorized_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: