> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up operator and brand identity in chat

> Inspect commercial identity, confirm changes, and follow proof and review status through a text-only v3 MCP client.

An account administrator can set the commercial operator domain and operating-unit code,
inspect an advertiser's linked brand, and follow supported verification work
through v3. The responses include the details in text; an MCP App or Settings
page is not required for these steps.

Commercial identity, public branding and domain proof are separate. Saving a
domain or finding a public `brand.json` does not grant account access, prove
domain ownership or authorise an agent to act for that brand. Organisation
names, login domains, users and roles are outside this setup flow.

## Select the confirmed identity contract

For this workflow, send `identityContract: "confirmed-v1"` on every operator
identity save or advertiser brand correction. Keep it on the preview,
confirmation and retry calls. Replacing an existing identity in this contract
requires a preview and the person's explicit confirmation; omitting the token
returns `CONFLICT` without saving.

This contract coexists with earlier v3 saves on the same tools. Calls using
only the earlier fields retain their existing behaviour, including one-call
Buyer operator and advertiser brand updates and the Seller's
`confirmOperatorDomainProfileReset` check. They do not acquire a new preview
requirement. New setup integrations should select `confirmed-v1` explicitly.
Supplying `preview` (including `false`), `confirmationToken`, or an advertiser
update's `publicBrand` also opts into the confirmed contract. An unsupported
`identityContract` value is rejected; it does not fall back to an earlier save.

## Find the account and its current identity

Call `tools/list`, then `get_status` with no `accountId`. Name the account ID
it returns as `accountId` on every call, and read status for that account. A
principal with several accounts must name the Buyer or Seller Account it
intends to configure. An Organisation-only account provides
orientation and support asks; it does not expose a separate identity editor.

| Active account | Read | What to inspect |
| - | - | - |
| Buyer | `get_status` | `operatorIdentity`: domain, source, unit, lock, domain proof and AAO evidence |
| Seller | `get({ "kind": "seller", "include": ["identity"] })` | Operator domain, verification, saved branding and the current human review |
| Buyer advertiser | `get({ "kind": "advertiser", "id": "ADVERTISER_ID" })` | `brandIdentity`: BrandRef, saved name, logo, manifest URL and provenance |

For a Buyer, `operatorDomainSource: "legacy_customer_domain"` identifies a
fallback from the account record. It is not a confirmation of the commercial identity.
`domainProof` reports the separately recorded verification and its source.
`organization` and `aao` describe the evidence used by readiness; inspect them
alongside `locked`.

Every Buyer `get_status` read looks the company up in the AgenticAdvertising.org
(AAO) registry, the same lookup the web app makes, and reports what it finds in
`aao`. The registry match never decides whether the company is verified: that
always comes from Apostra's own domain evidence. The lookup has a short
deadline. When the registry is slow or fails, `aao.status` is `unavailable` and
the read still answers.

`blockers` lists exactly the setup checks that block buying and are not yet
complete, in checklist order: `operator_identity`, `terms`, `account_standing`,
`payment_terms` and `billing_details`. `billing_details` blocks while the
billing organization lacks a payer name, street address or country and the
buyer buys from a consolidated-billing seller. `state` and `nextActions` read
the same checks, so a blocker never appears in one and not the other.

### Read the whole setup checklist and every seller

A Buyer `get_status` call can return the full checklist and one row per seller.
Send `include: ["setup"]`. Only a Buyer account accepts it: any other account
kind is refused with `VALIDATION_ERROR` naming `include`.

| Field | What it holds |
| - | - |
| `setup.checks[]` | Every check, complete or not: `id`, `name`, `description`, `status` (`complete`, `missing` or `blocked`), `isBlocker`, `action` and `actionLabel`. `blockers` is these rows filtered to the incomplete blockers, so the two always agree. |
| `setup.sellers` | One page of sellers: `total`, `returned`, `truncated` (true while another page follows), `nextCursor` and `results[]`, 25 to a page. |
| `setup.sellers.results[]` | `sellerAccountId`, `name`, `providerType` (`null` for an AdCP seller), `billingRoute` (`direct` or `interchange`), `status`, `canBuy`, `reason`, `action` and `actionLabel`. |
| `setup.commercial` | `billingDetailsComplete`, and `missing` with any of `TOS`, `STANDARD_RATE_CARD` and `BILLING_DETAILS`. |
| `setup.firstCampaign` | `launched` and `action`, or `null` when Apostra could not read it. |
| `setup.billingCompany` | The company name on the billing organization. |
| `operatorIdentity.suggested_operator_unit` | The unit code, as `{ "id": "..." }`, the account would get if saved now, or `null`. Present only with `setup`. |
| `operatorIdentity.accountName` | The account's name. Present only with `setup`. |

A seller's `status` is one of `ready`, `seller_unavailable`,
`needs_connection`, `needs_account_selection`, `needs_advertiser_mapping`,
`needs_commercial_setup` or `platform_blocked`.

Every `action` is a tool call, `{ "tool": "...", "arguments": { ... } }`, or
`null` when there is nothing to call (the check is complete, or you cannot
manage the identity). It is the same shape as `nextActions`:

| Step | Action |
| - | - |
| `operator_identity` | `save_buyer_operator`, with the `operatorDomain` you choose. `save_ask` when the identity is locked. |
| `terms`, `account_standing`, `billing_details`, a seller needing commercial setup | `open_page` with `{ "page": "plan_and_billing" }` |
| `payment_terms`, a seller needing only payment terms | `save_billing` with `{ "paymentTerms": "net_60" }`, the default; the person may choose another |
| `media_partners` | `open_media_partners_page` |
| A seller needing an advertiser mapping | `open_media_partners_page` with `{ "sellerAccountId": ... }` |
| A seller needing a connection or account choice | `save_connection` with `{ "sellerAccountId": ..., "authorization": {} }`, which returns the authorization URL |
| `firstCampaign` | `open_campaigns_page` |

When `sellers.nextCursor` is present, call `get_status` again with
`include: ["setup"]` and `sellersCursor` set to it for the next page. Send
`sellersCursor` only with `include: ["setup"]`. A cursor that is malformed,
stale because the seller list changed, or issued for another account is refused
with `VALIDATION_ERROR` naming `sellersCursor`; start again from the first page.
The text of the response lists every check and every seller on the page, each
with the tool to call, and ends with the `sellersCursor` for the next page.

For an advertiser, `source: "saved_brand_link"` means a saved branding snapshot.
It does not assert that a published document is available now. A Seller's
`identity.source: "storefront_config"` likewise describes saved configuration.
`manifestUrl: null` means that this read cannot identify a published URL.
`brandRef.domain` is the saved brand domain as a lowercase host, without a
scheme, port or path. The advertiser's `brand` field keeps the domain exactly
as it was saved. When no valid domain can be read from the saved value,
`brandIdentity` is null and the rest of the advertiser still loads.

## Confirm the Buyer's operator

Use `save_buyer_operator` as an account administrator:

```json theme={null}
{
  "identityContract": "confirmed-v1",
  "operatorDomain": "agency.example"
}
```

Every Buyer account is its own AdCP operating unit, so there is no scope to
choose and no question about whether the account is "the whole company". An
organisation with one Buyer account has one unit; an organisation with several
accounts has several. The unit's `name` is the account name, and its `id` is a
short code fixed when the identity is first saved:

* Omit the code and one is generated from the account name, for example
  "DM - Restaurant Company Europe" becomes `dm-restaurant-company-europe`. If
  another account under the same operator domain already holds it, a short
  numeric suffix is added (`-2`, `-3`).
* To choose a shorter code, send AdCP `operator_unit` with the `id`, for example
  `"operator_unit": { "id": "rce" }`. Use something your company already uses.
  Never use `default`, `global` or `primary`: no account is the default,
  global or primary one, and `save_buyer_operator` refuses those codes.
* Saving again without a code keeps the saved one. The code never changes when
  the account is renamed.

`get_status` reports the saved unit as `operatorIdentity.operator_unit`, with
`name` taken from the buyer account name.

Saving checks AAO for the domain's canonical identity. If AAO records an alias,
the canonical domain becomes the seller-facing key and the submitted spelling
is retained. No AAO record uses the normalised submitted domain. If AAO times
out or is unavailable, the save still succeeds with that normalised domain as a
provisional key; this does not prove domain control.

## Join or leave the seller-history co-op

`save_buyer_operator` also carries `sellerHistoryCoop`, the account's
membership in the [seller-history co-op](https://docs.apostra.com/v2/concepts/seller-history-co-op.md), how
an account takes part in the data co-op of Section 6.3 of the Terms of Service. It
starts `false`, and `get_status` reports the current value as
`operatorIdentity.sellerHistoryCoop`. The setting is part of the operator
identity because the operator that owns the account decides, so it takes the
same administrator permission.

To change only the membership, send the saved identity again unchanged with
`sellerHistoryCoop` set. Joining shares the account's judged results as
aggregates, and leaving stops the account reading pooled history and sharing new
buys while what it already shared stays in the pool, so either change needs the
preview and confirmation below. The preview returns
`preview.sellerHistoryCoop` with the before and after values and a link to the
page that says what a member shares and gets. Show that to the person before
confirming.

## Confirm an identity change

Before replacing an existing domain or unit code, send the proposed
values with `preview: true`. This explicitly requests `action: "preview"`
without saving. Show the person the before and after values, including the unit code the save
will use, and any loss of domain proof. After they confirm, repeat the same request without `preview`
and with the returned `preview.confirmationToken`.

If you did not name a code and another account under the same domain takes the
previewed one before you confirm, the save is refused with `CONFLICT` and saves
nothing. Preview again to see the new code, and confirm that one.

In `confirmed-v1`, an unconfirmed replacement that does not request a preview
returns `isError: true` with `code: "CONFLICT"` and saves nothing. A successful
preview is returned only when you explicitly request one. A caller that
attempts an unconfirmed replacement in this contract receives an error rather
than a successful-looking result that preserves the old identity.

The confirmation belongs to that account, current identity and requested
change. A stale or different confirmation fails with `CONFLICT`; read and
preview again. An identical retry with no new proof returns `unchanged`,
preserves confirmation timestamps and domain proof, and does not create a
second identity audit event.
A successful write returns a resource receipt and its audit ID.

Read `get_status` again to check the persisted domain, unit and proof. The
first advertiser binding locks the operator identity. A locked correction
requires support, rather than a different domain spelling or another account.

The operator domain must be a registrable domain, such as `agency.com` or
`agency.co.uk`, not a subdomain such as `team.agency.com`. Sellers check the
operator against the brand's `trust.json` grants, which name registrable
domains, so no seller could open an account for a subdomain operator.
`save_buyer_operator` refuses a subdomain, in a preview too, with
`VALIDATION_ERROR` on `operatorDomain`, and saves nothing. While the confirmed
identity is not yet locked, `save_advertiser` also refuses to create an
advertiser for a subdomain operator and creates nothing. Set a registrable
domain with `save_buyer_operator`, then create the advertiser.

An identity already locked to a subdomain keeps it: `save_buyer_operator` still
accepts that same domain, for example to set the unit code. No seller can open
an account for it, so each media partner shows its advertisers with
`requiredAction: "fix_operator_domain"` (see
[Where the advertiser's account with a media partner stands](https://docs.apostra.com/v3/media-partner-availability.md#where-the-advertisers-account-with-a-media-partner-stands)).
Request support through `save_ask` to move it.

One change is still open on a locked identity. An identity saved before every
account had a unit can be locked by an advertiser while it has no unit:
`get_status` reports `locked: true` and `operator_unit: null`. Repeat
`save_buyer_operator` with the same `operatorDomain` to give it its unit, with an
optional `operator_unit` code. No preview is needed. This changes the key
sellers receive, so it is refused with `CONFLICT` while a seller account already
holds the key without a unit: a complete-key Seller account, or an external
seller account linked to one of this account's advertisers. Request support
through `save_ask` to move those accounts onto the unit.

A verified account that still has no unit is not blocked from setup, but
`get_status` offers choosing its code as a recommended next action
(`operator_unit`, `save_buyer_operator`), because opening a seller account is
refused without one. The `operator_identity` check stays complete.

New Seller connections can then set up the account. A different domain on a
locked identity still requires support.

## Opening seller accounts needs an operating unit

Every Buyer account is its own operating unit. Before a seller account can be
opened for this Buyer account, its operator identity must have an
`operator_unit`. This holds on every route that opens a company seller account
for a seller on complete-key accounts: AdCP `sync_accounts`,
automatic activation, and the support operation that requests
accounts for earlier per-user grants. An identity with no unit yet (saved before
every account had one) is refused with the correctable `ACCOUNT_SETUP_REQUIRED`
error and `details.reason: "operator_unit_required"`. Nothing is created, and
the same request succeeds once the unit is set.

Set the unit with `save_buyer_operator`, as described above. If an advertiser
has locked the identity and seller accounts already use it, request support
through `save_ask`. Seller accounts that already exist are not changed.

## Resolve and link public branding

In a Buyer Account, call `save_advertiser` with only:

```json theme={null}
{ "resolveBrand": "brand.example" }
```

This previews a brand before you create an advertiser, the same way the
advertiser form's **Look up brand** does. It reads the brand's AAO registry
record of any kind, including community records, then enrichment data and the
brand's own website. `identity.source` is `brand_json` or `hosted` only when
the brand publishes its own identity, which is what `publicBrand: true`
requires; `null` means the name and logo came from another source. The lookup
answers within about eight seconds. If it is still running at that point, the
call returns an unresolved card with `pending: true`; call it again shortly
and it picks up the same lookup. Media Company accounts that create their own
advertisers use the same call.

In a Seller Account, `save_seller` with only `resolveBrand` looks up the
Seller's own public identity. That lookup requires a published `brand.json` or
an AAO-hosted brand identity and does not fall back to inferred branding.

Both lookups are read-only. A Seller Account's brand name, logo and logo
background are refreshed from the same published identity when you save the
operator domain with `save_seller`, whether or not the domain changed. If the
brand has no published identity, or the lookup is slow or unavailable, the
domain save still succeeds. When the domain is unchanged, the stored brand is
left as it was. When the domain changes, any of the three that the new domain
does not publish is cleared with the rest of the previous domain's profile,
so the old brand's logo never stays beside the new domain. Previews
never write. Inspect `resolved`, `identity.brandRef`,
`identity.source`, name, logo and `manifestUrl`. `identity.brandRef` is an AdCP brand reference; it has
no `brand_id` when the domain has no house brand id, or when the published id
is not a valid AdCP brand id. An unavailable registry
returns an error; a missing
brand returns `resolved: false` and may supply a builder link.

To create a Buyer advertiser that requires public branding, call
`save_advertiser`:

```json theme={null}
{
  "name": "Example brand",
  "brand": "brand.example",
  "publicBrand": true,
  "idempotencyKey": "example-brand-setup-1"
}
```

This create operation does not accept `identityContract`; use that field only
when correcting the brand of an existing advertiser.

Keep the key and creation fields unchanged when retrying. A key reused for a
different request is rejected. The creation receipt includes its audit event
ID. Existing calls that omit `publicBrand` retain the normal brand-resolution
behaviour, which may include inferred branding.

If a brand-resolution source is rate-limited or temporarily unavailable while
you create an advertiser, `save_advertiser` returns `SERVICE_UNAVAILABLE`
instead of treating the brand as missing. Wait and retry the request; the
error includes `retryAfter` in seconds when the source supplied one. No
advertiser is created for that response. A missing-brand validation error
means every resolution source answered without data.

To correct an advertiser's brand, supply `identityContract: "confirmed-v1"`,
`advertiserId`, `brand`, `preview: true`, and optionally `publicBrand: true`.
An account administrator must review the returned preview and repeat the
request without `preview` and
with its top-level `confirmationToken` after the person confirms. An
unconfirmed replacement in this contract without `preview: true` returns
`CONFLICT` and saves nothing. A saved replacement returns
`advertiser.identityChangeReceipt` with its audit ID and brand-link IDs. Read
the advertiser again to verify `brandIdentity`.
If `brandIdentity` is null, the saved link cannot be inspected safely. Request
support with `save_ask` to repair that legacy record before replacing it.

An identical brand-only retry does not rewrite the link. With `publicBrand:
true`, it also checks that public identity is currently available and returns
that lookup separately from the saved snapshot. It does not refresh the saved
name, logo or document contents.

> [!NOTE]
>
> v3 can resolve public branding and link an advertiser to a resolved brand.
> It cannot create, edit or publish `brand.json`, choose who owns an AAO-hosted
> document, or refresh a saved snapshot in place. Use the domain publisher or
> the returned external builder for document changes. These responses report
> `editingSupported: false`. A public lookup is never domain proof.

### Change an advertiser's brand after first use

An advertiser's brand can still change after its first campaign or
seller-account binding. The change applies to future buys only. The advertiser
keeps its ID, name, campaigns, ledger, measurement, reporting, creatives,
labels and tracking, because none of them are tied to the brand.

* **Buys already sent to a seller** keep the seller account they were booked
  under. Updates, cancellation, delivery reporting and creative sync for those
  buys stay on that account until the buy ends.
* **Draft and unsent buys** go out under the new brand when they are sent.
* **Sellers** hold their accounts by brand. The first buy under the new brand
  at each seller creates or finds an account for that brand, and the seller
  decides on it separately: an approval for the earlier brand does not carry
  over. Accounts held under the earlier brand are not changed, revoked or
  deleted.
* **Accounts you own** on a platform, such as an ad account connected with
  OAuth, stay linked. Links to accounts a seller provisioned under the earlier
  brand are not used for buys under the new one.
* **Brand data** follows the new brand: the effective brand profile, creative
  sessions started afterwards and property-targeting defaults. A creative
  session, campaign creative or media buy that saved its brand earlier keeps
  what it saved. Looking an advertiser up by brand domain matches the current
  brand only.
* **Currency and sandbox stay fixed.** A different currency or sandbox state is
  a different advertiser.
* **A brand an advertiser has used stays reserved to it** for as long as the
  advertiser exists, archived included, even after its last buy on that brand
  ends. Another advertiser cannot be created on it or moved onto it, and the
  refusal names the advertiser that holds it. Deleting an advertiser releases
  its brands.
* **Two exceptions to that reservation.** An archived sandbox advertiser
  releases every brand it has used, the current one and earlier ones, so
  another sandbox advertiser can take one of them. Restoring the archived
  advertiser is then refused while another active sandbox advertiser holds one
  of those brands. An archived production advertiser keeps all of its brands.
* **Changing back reuses earlier seller accounts where their links still
  exist.** An advertiser holds one active link per seller agent. If a newer
  link at the same seller agent replaced the link a seller provisioned under
  the earlier brand, changing back does not restore it: that seller account may
  need to be found or provisioned again. Seller accounts themselves are not
  deleted.

No proof of brand ownership is needed beyond what creating an advertiser for
that brand needs. Sellers check the brand's own `brand.json`, as they do for a
new advertiser.

Use the same confirmed flow as a correction: `identityContract:
"confirmed-v1"`, `advertiserId`, `brand` and `preview: true`, then the same
request without `preview` and with the `confirmationToken` after the person
confirms. Only an account administrator can make the change. For a brand
change, the preview also returns `impact`, so the person can see what the
change touches before confirming:

| Field | Meaning |
| - | - |
| `impact.appliesTo` | Always `future buys`. |
| `impact.liveBuysStayOnCurrentBrand` | Buys already sent to sellers that stay on the current brand until they end: `count`, and up to ten `mediaBuyIds`. |
| `impact.undispatchedBuysMoveToNewBrand` | Draft and unsent buys that will be sent under the new brand: `count`, and up to ten `mediaBuyIds`. |
| `impact.sellersWillSeeNewAccount` | Sellers that will see a new account for the new brand: `count`, and up to ten `sellers`. A seller is identified by its Account id (`kind: "storefront"`, with `sellerAccountId` and its `name` when known) or is a sales agent your sent buys reach that no Storefront stands for (`salesAgentId`). A seller reached both ways is listed once. Only sellers linked under the current brand count. A seller you reach only through an older direct sales-agent connection (not a Storefront), with no buy sent there yet, is not listed. `legacyConnections` is how many such connections the advertiser has, and `complete` is `false` whenever there is at least one, meaning the list may be missing a seller that will also see a new account. |

The token is bound to the brand link the preview was computed from and to its
complete impact, every buy and seller rather than only the ids the preview lists.
If the brand changed after the preview, or a buy was sent to a seller since, the
confirmation is refused as stale; preview again. A buy that was sent to a seller without a stored seller account would
otherwise look up its account under the new brand, so the change stores that
account first, from the brand it is still on, completed buys included. If a
live buy's account cannot be worked out with certainty, for example because its
seller account is no longer linked to the advertiser, the change is refused and
the error names the buy. Nothing is changed in that case: fix what the error
says, then try again. A buy that has already ended (completed, canceled,
rejected or failed) and whose account cannot be worked out does not block the
change: it will not be updated or canceled again, so it is skipped and listed
in the advertiser's audit history. A later delivery or status read for that
ended buy may then look its account up under the new brand.

An advertiser that a seller reaches through external self-serve access cannot
change its brand, because that access is pinned to the current brand and would
be retired for good. Create a new advertiser for the new brand instead. The
preview refuses it too.

An advertiser that a seller has on its advertiser roster under its current brand
also cannot change its brand yet. The seller's roster matches the advertiser
by brand, so the entry would stop resolving and the seller would see the
advertiser as unresolved. The preview and the confirmation both refuse it. Create
a new advertiser for the new brand, or ask the seller to remove the roster entry
first.

A brand change after first use goes through `save_advertiser` with the preview and
confirmation above.

## Confirm the Seller's operator and follow proof

Use `save_seller` as an account administrator:

```json theme={null}
{
  "identityContract": "confirmed-v1",
  "operatorDomain": "seller.example"
}
```

For a replacement, include `preview: true` before any fields change. Review
the previous domain, proof and profile values, then repeat without `preview`
and with its top-level `confirmationToken` after the person confirms. An
unconfirmed replacement in this contract without `preview: true` returns
`CONFLICT` and saves nothing. Fields curated for the previous domain, including
description, channels, membership status, website, brand name, logo and logo
background, may be cleared. Description and channels can be supplied in the same request to
replace those values; the other legacy profile fields are not writable in v3.
An intervening identity or profile change requires a fresh preview. The older
`confirmOperatorDomainProfileReset` flag alone does not replace a confirmation
token in `confirmed-v1`; its earlier behaviour remains available to existing
callers as described above.

The save returns a receipt with its audit ID and, when present, the domain
review ID. Saving the domain uses the existing verification service and human
review queue. An already approved domain may verify immediately; otherwise the
current review is visible through:

```json theme={null}
{ "kind": "seller", "include": ["identity"] }
```

Inspect `operatorDomainVerified` and `identity.domainProof.review`. The review
contains its ID, domain, status, request time, revision, decision time and ETA
when assigned. Poll this same read after the human responds. `PENDING` is not
proof; `REJECTED` requires a correction or support follow-up. Verification is
complete when the persisted domain matches the intended domain and
`operatorDomainVerified` is true. A repeated identical save does not duplicate
the review. Apart from the brand name, logo and logo background described
above, saving a Seller domain does not copy the public lookup's document fields
into Seller configuration.

A domain-only save for the already saved domain returns `unchanged`; members
can make that request without administrator write permission or a proof-review
read. Actual domain changes require an administrator. Proof reads are
cancellable and have a bounded wait. If a transient read failure returns
`SERVICE_UNAVAILABLE`, retry `get`.

## Request a person when proof or correction needs help

Show the person the proposed support request before filing `save_ask`:

```json theme={null}
{
  "kind": "create",
  "type": "support",
  "title": "Verify our commercial operator domain",
  "detail": "Our saved operator is agency.example. We need help completing domain proof. Please provide the verification steps."
}
```

Keep the returned `askId`. In a Buyer or Seller Account, poll it with
`get({ "kind": "ask", "id": "ASK_ID" })`, follow the human's instructions,
then re-read identity. A filed or resolved ask does not itself verify a domain.
Do not paste passwords, OAuth tokens or other secrets into chat.

If support approves a Buyer domain, repeat `save_buyer_operator` with the saved
domain to record that approval, then check `get_status` again. The
save checks approved evidence separately; repeating domain text without that
evidence cannot verify it.

A DNS TXT record check runs from the account's domain settings, not from v3.
v3 can inspect the evidence and request human help. A Seller review with no current
record, a rejected review, or a locked identity correction can use the same
support path. [Shared workflows](https://docs.apostra.com/v3/shared-workflows.md) covers ask
states and follow-up.


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