Skip to main content
AdCP distinguishes four entities in every billable operation:
EntityQuestionHow identified
BrandWhose products are advertised?Brand reference: domain + optional brand_id (brand.json)
AccountWho gets billed? What rates apply?Account reference
OperatorWho operates on the brand’s behalf?Domain (e.g., pinnacle-media.com)
AgentWhat 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 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 the account 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.
BillingWho is invoicedUse case
operatorOperator (agency or brand buying direct)Operator buying on their own terms
agentAgentAgent consolidates billing across brands
advertiserAdvertiser directlyOperator places orders but advertiser pays (common on social platforms and in DACH B2B workflows)
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 trueexplicit 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. This only applies to operator-billed accounts. Example capabilities:
{
  "account": {
    "require_operator_auth": false,
    "supported_billing": ["operator", "agent"]
  }
}
Sellers that support advertiser billing declare it explicitly:
{
  "account": {
    "require_operator_auth": false,
    "supported_billing": ["operator", "agent", "advertiser"]
  }
}
These fields combine into common patterns.

Seller patterns

Which kind of platform are you buying from? That determines the account setup pattern.
Platform typerequire_operator_authsupported_billing
Social / walled gardentrue["operator"]
Direct publisherfalse["operator"] or ["operator", "agent"]
DSP / programmaticfalse["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:
{
  "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:
{
  "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:
{
  "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:
{
  "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:
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "billing": "operator"
  }]
}
Seller acknowledges the request but requires setup before provisioning:
{
  "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:
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "billing": "operator"
  }]
}
Seller rejects the request because this operator has no direct billing relationship:
{
  "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:
{
  "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:
{
  "accounts": [{
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com",
    "billing": "agent"
  }]
}
Account active immediately:
{
  "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.
{
  "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": ["*"]
    }
  ]
}
FieldRequiredDescription
domainYesOperator’s domain
brandsYesBrand IDs this operator can represent. ["*"] means all brands.
countriesNoISO 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:
FlagWhat it tells the seller
brand (domain + optional brand_id)Which brand is advertising
operatorWho operates on the brand’s behalf (agency, trading desk, or the brand itself)
billingWho gets the invoice — operator, agent, or advertiser
billing_entityStructured 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_termsPayment 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.
sandboxWhether 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.

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:
    • 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 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, 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 for the full request/response schema.

Account status

StatusMeaningNext step
activeReady to usePlace buys on this account
pending_approvalSeller reviewingHuman may need to visit setup.url. Poll list_accounts for updates.
rejectedSeller declined the requestReview rejection reason, adjust and re-sync, or contact seller
payment_requiredCredit limit reachedAdd funds or route spend to other accounts
suspendedWas active, now pausedContact seller
closedWas 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:
ScopeMeaningExample
operatorOne account for all brands under this operatorAgent sends (Pinnacle Media, Acme) and (Pinnacle Media, Nova) — seller maps both to the Pinnacle Media account
brandOne account for this brand regardless of operatorAgent sends (Acme, Pinnacle Media) and (Acme, Summit Agency) — seller maps both to the Acme account
operator_brandDedicated account for this operator+brand pairAgent sends (Pinnacle Media, Acme) — seller creates a specific Acme-via-Pinnacle account
agentThe agent’s default accountAgent 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

CodeWhen returnedResolution
ACCOUNT_REQUIREDMultiple accounts; seller can’t determine whichPass account_id in the account reference
ACCOUNT_NOT_FOUNDaccount_id doesn’t exist or agent lacks accessCheck account reference, re-run sync_accounts
ACCOUNT_SETUP_REQUIREDNatural key resolved but account needs setupCheck details.setup for URL/message
ACCOUNT_AMBIGUOUSNatural key resolves to multiple accountsPass account_id or more specific natural key
PAYMENT_REQUIREDCredit limit reached or funds depletedAdd funds, route to another account
ACCOUNT_SUSPENDEDAccount not in good standingContact seller
BRAND_REQUIREDBillable operation without brand referenceInclude brand in request
When the seller returns ACCOUNT_REQUIRED, it includes the available accounts:
{
  "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:
{
  "house": {
    "domain": "local-bakery.com"
  },
  "authoritative_location": "https://registry.agenticadvertising.org/brands/local-bakery.com"
}