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

# Listing, Playbook, and AI Business Rules

> The Setup listing buyers see, the playbook used to merchandise, and the AI-assisted rules that govern storefronts.

There is no separate `merchandising` document. **Merchandising is what your
storefront *does*** — the [Merchandising Agent](/v2/concepts/storefront-agent)
selling on your behalf — not a thing you save. Three Pages steer it, split by
what kind of fact each one holds:

| Page                                                        | Kind                                    | Owns                                                                                                                                                            |
| ----------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Listing (previously called the media kit or Discovery Card) | storefront projection — what buyers see | Resolved name/logo, short description, buyer-visible channels and accepted countries, plus optional Advertising Policies                                        |
| `playbook`                                                  | judgment — how you sell                 | Value-anchor pricing facts (floor/target/ceiling per condition), brand and operator house discounts off those prices, posture, packaging and selection guidance |
| `business_rules`                                            | constraint — what storefronts accept    | Brief Acceptance, Creative Policy, approval gates (`creativeApproval`, `mediaBuyApproval`), and approval routing                                                |

The Page names and read tools now use the same three nouns. **Selling Terms** is retired:
structured prices and floors moved to Playbook, while approval gates moved to
AI Business Rules. **Business Profile** is compatibility storage, not a Page and
not a Listing. Product Marketing material — what the industry calls a "media
kit," since it can include audience stats, products, and rates — is a separate
input to merchandising; that product is not a V3 noun until its backing store
exists. Your saved configuration and version history do not move or reset.

In chat and any MCP Apps host, `get_discovery_card`, `get_playbook`, and
`get_business_rules` open the portable Pages. `get_discovery_card` opens the
canonical Listing through the `media-kit` resource URI. The deprecated
`get_media_kit` continues to open the legacy Business Profile through the
separate `business-profile` resource URI, for saved-conversation
compatibility only; it is not the Listing Page.

On the `/mcp/v3` surface, the deprecated compatibility kind behind
`get_media_kit`/`save_media_kit` is spelled `listing` (its canonical wire
name as of 2026-09-01); `media_kit` is still accepted as a deprecated alias
for the identical, still-deprecated handler. Despite the name, it still
returns the legacy Business Profile, not the canonical Listing — use
`get({ kind: "seller", include: ["listing"] })` (`discoveryCard` is a
deprecated alias) and `save_seller` (its `listing` field, with `mediaKit`
as a deprecated alias) for that.

<Note>
  `/mcp/v3` is available to every authenticated Seller Account. The `get` and
  `save_*` examples below apply to that preview surface; the active account's
  permissions, resource access, and independent capability gates still determine
  which tools are available. The `get_discovery_card`, `get_playbook`, and
  `get_business_rules` Pages are also available through the current seller MCP
  surface.
</Note>

## One fact, one owner

Playbook instruction content cannot duplicate structured Playbook pricing,
Buyer Discounts, Listing facts, or AI Business Rules. It may refer to those
facts without restating them. A conflicting version is rejected on save rather
than left for the agent to reconcile later. Writing "the Nike house gets 12%"
into your guidance is the case this catches: the discount is a structured rule
with its own field, not a sentence for the agent to interpret.

AI Business Rules separates three decisions. **Brief Acceptance** decides whether
an otherwise matching brief should receive a response; **Creative Policy**
judges submitted creative; and Approval **Mechanics** decides whether and where
human review runs.
With `mediaBuyApproval: manual` and no active policy, every media buy is held
for review. Listing channels/countries and Playbook guide what the agent
presents and how it sells; neither controls the review gate.

A seller may separately elect to show Brief Acceptance, Creative
Policy, both, or neither as **Advertising Policies** on the listing.
This disclosure selector belongs to AI Business Rules because that is where its
source text is edited. Approval mode, approvers, routing, per-buyer overrides,
and revision notes are never disclosed. Listing can publish the selected local
AI Business Rules text. For a pass-through storefront, that buyer-visible
disclosure also includes any independently applicable policy text returned by
the connected sales agent, so the Listing never hides a downstream
restriction.

<Tip>
  Starting from a publisher's media kit, rate card, property roster, policy, or
  operations exports? Use the [publisher onboarding starter
  kit](/v2/setup/publisher-onboarding-starter-kit) to request the complete
  evidence pack, then put each confirmed fact in the owning Page below.
</Tip>

<Note>
  Listing includes local AI Business Rules. Brief Acceptance is evaluated before
  eligible work is forwarded to the connected sales agent. Channels and
  countries still come from the upstream agent's `get_adcp_capabilities`
  portfolio and remain read-only; missing declarations stay unknown and do not
  match filtered searches. Saving, enabling, and evaluating AI Business Rules is
  not an IU-rated activity today; other qualifying activity remains governed by
  the organization's accepted IU Rate Card.
</Note>

## Reading singleton nouns and the storefront projection

`playbook` and `business_rules` are singletons — exactly one per storefront —
so `get` identifies them by `kind` alone. Listing is not a noun; request
it as a projection of the storefront:

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

```json theme={null}
{ "kind": "business_rules" }
```

```json theme={null}
{ "kind": "playbook", "include": ["versions"] }
```

`include: ["versions"]` adds the full version history for `playbook` and
`business_rules` (an array of `{ version, isActive, notes, createdAt }`); omit
it and `get` returns only what is currently active. There is no separate
"list versions" tool — version history is an `include`, the same way source
diagnostics are.

The history is deliberately a summary: it carries no `content`, because a
storefront with dozens of 50,000-character versions would return an unusable
response. To read one version in full, name it:

```json theme={null}
{ "kind": "playbook", "version": 2 }
```

That returns `selectedVersion` with the version's `content`, its `notes`, its
`ownershipIssues`, and a `restorable` flag. `restorable: false` means the
version restates a fact owned by another canonical surface and would be rejected
if you saved it back unchanged; the create-and-activate section below explains why, and
what to do about it.
`version` is only served for `kind: "playbook"`.

`include: ["discounts"]` adds the playbook's brand and operator
[house discount](/v2/storefront/house-discounts/overview) rules. If a read
fails, the key is omitted **and** an `unavailableIncludes` entry names it —
"no discounts are configured" and "the discount read failed" mean opposite
things for what your buyers are paying, so the response never lets you confuse
them.
`include: ["approvalRouting"]` on `business_rules` adds who receives each kind
of review work — see [Approval routing](#approval-routing-who-reviews-as-opposed-to-whether-anyone-reviews).

## Listing — what buyers see

`save_storefront` writes the local description and, for a managed storefront,
the buyer-visible channels and ISO countries. Pass-through storefronts read channels
and countries from `get_adcp_capabilities`; those fields are read-only locally.

```json theme={null}
{
  "description": "Regional lifestyle and news network reaching auto intenders across France.",
  "channels": ["display", "ctv"],
  "countries": ["FR"]
}
```

Name, logo, colors, and website are resolved from the verified brand domain
and `brand.json`; link or correct that identity instead of forking it on the
listing. Open the Page with:

```json theme={null}
{ "page": "discovery_card" }
```

### Listing — four steps and destinations

In the seller UI, this page is labeled **Listing** (the retired **Branding &
distribution** page redirects here). It has four steps, completed in order:

1. **Identity.** Name and logo resolved from the verified brand domain and
   `brand.json`; link or correct that identity instead of forking it on the
   listing.
2. **What you do.** Description, buyer-visible channels, accepted countries,
   and the selected Advertising Policies disclosure. This is shared input
   for every destination below, not a ChatGPT-only listing.
3. **Listed on Apostra marketplace.** A switch included with every
   account. Live means buyer agents can find the storefront on Apostra;
   turning it off unlists the storefront everywhere, with a confirmation.
4. **Public distribution.** The gate for every app destination. Add the
   Distribution package (the paid upgrade — see what it costs before
   committing), then point your domain at Apostra with one CNAME at your
   registrar; Apostra checks it for you. Once the domain verifies, choose
   **Make it public** to take Public distribution live — verifying alone does
   not publish — and verifying requires the storefront to already have a
   published Apostra listing (step 3). Apostra
   first-party storefront is attached to platform-managed hosting
   automatically and never shows this step.

For an active, unexpired [Demo Storefront](/v2/storefront/inventory-sources/demo-storefront),
only **Identity** and **What you do** count towards Demo readiness. **Listed on
Apostra marketplace** and **Public distribution** remain available but are
labelled optional. Making either choice can change where the synthetic listing
appears, but the Demo does not publish itself or add a paid Distribution
package just to show as ready. Expired and inactive Demos use the ordinary
four-step Listing model.

There is no segmented Apostra/Public control and no selectable "Hidden"
option — a hidden storefront can never be found, so it isn't a distribution
choice. A storefront that hasn't published yet is shown as a state, **Not
listed yet**, never as a step to pick. An already-listed storefront returns
to **Not listed yet** by turning off the **Listed on Apostra
marketplace** switch (step three), with confirmation. Unlisting takes the storefront off
Apostra and the public web; buyers and directories can no longer find it
until it's published again. It does not disable an app still served from its
own legacy per-app hostname: that app keeps serving until the hostname is
disabled or the app moves to your public listing domain.

Below the four steps, **Distribution** shows the destinations where buyers
and the seller's own team find or reach the listing, as tabs: the **Listing
card** (a downloadable PDF) comes first and its tab is always shown. Download
becomes available once your listing is live on Apostra (step 3) — the
card always matches what is live, so there is nothing to download before
then. App destinations — **ChatGPT app** and **Claude** — unlock once
Public distribution (step 4) is live; before then, the app-destination tab
is dimmed with a note that apps unlock with that step.

The messaging destinations — **Slack**, **Microsoft Teams**, **Discord**, and
**WhatsApp**, in that order — are how the account's own linked, active
Apostra members reach Murph, not a buyer-facing surface, so they are
never gated by Public distribution: Slack's tab always shows, either
**Install available** (one click, no setup) or **Coming soon**. Microsoft
Teams, Discord, and WhatsApp become tabs once the account is in early access
for branded apps; until then they stay in the **+ Add destination** picker
with an **Early access** chip and a line on what each needs before you
commit. Adding a branded destination is a prerequisite-plus-artifact flow:
what it needs, then the thing you take away.

OpenAI's own domain-ownership proof (the challenge token OpenAI gives you) is
one of the ChatGPT destination's own steps, not a control on the Public
distribution step. See [Create a white-label ChatGPT app](/v2/setup/chatgpt-app-setup)
for the full ChatGPT walkthrough, including what happens on OpenAI's side
after you submit.

The remaining controls on the page:

* **Brand domain.** Save the domain this storefront operates as. See
  [Domains](/v2/concepts/domains) for what the brand domain does and how
  alias verification works. Saving is explicit — there is no autosave.

* **Verification status.** Instead of an unexplained "Verify" button (that
  action is Apostra-admin only), the page shows the plain-language reason your
  domain is pending — for example, that your account has no registered domain
  yet, or that the registered domain still needs member-email or Apostra
  approval. This is explanatory text on the page itself, not a link; see
  [Domains](/v2/concepts/domains) for the full rules. Once verified, buyers
  can trust the identity resolved above.

* **Brand identity.** The top card starts with the operator domain and shows
  where identity came from: a website-hosted `brand.json`, an AAO-managed
  identity, a community fallback, or no file. A website-hosted file remains
  read-only here. When no file exists, the AAO builder offers self-hosting or
  managed hosting after domain verification. This is optional; channel assets
  can still be uploaded directly.

* **ChatGPT app destination.** Once Public distribution is live, **Request
  access** requests reviewer access; after activation, the storefront
  receives its no-spend reviewer advertiser and **Download your OpenAI
  package** appears. Prove domain ownership to OpenAI from the same tab by
  pasting the token OpenAI gives you. There is no dedicated ChatGPT hostname,
  app-store category selector, or submission-review checkbox on this
  platform — Apostra sets the directory category to Business for new
  registrations (an existing registration keeps its saved category) and shows
  the value before you download, and OpenAI's review happens in OpenAI's own
  portal. See
  [Create a white-label ChatGPT app](/v2/setup/chatgpt-app-setup) for the
  full walkthrough, including what happens on OpenAI's side after you
  submit.

  Storefronts with icons uploaded before automatic generation was introduced
  continue serving those existing icons during migration. New registrations
  use resolved brand identity automatically until the owner uploads both
  channel-specific icons.

  An existing app still served from its own legacy per-app hostname keeps its
  ChatGPT destination (and Claude's, since the same public endpoint already
  exists there) reachable before Public distribution goes live: an existing
  reviewer grant, your token, and the package stay available and maintainable
  without waiting on step four (requesting a *new* reviewer grant still needs
  Public distribution and the Distribution package) — move to your public
  listing domain there when you're ready; the app keeps working until then.

* **Claude destination.** Once Public distribution is live, the Claude tab
  confirms that the storefront's public MCP endpoint is ready and links to
  Anthropic's Connector Directory submission walkthrough. OAuth and the
  storefront's server-side permissions still decide which accounts and tools
  each person can access. Anthropic's policy checks, directory review, and
  approval remain separate from endpoint readiness. In Anthropic's
  **Authentication** step, select **OAuth 2.0 + Dynamic Client Registration**;
  the public MCP endpoint publishes the rest of the OAuth metadata. In **Test
  & launch**, choose **Open reviewer login** in Apostra and paste that
  email and password into the protected test-setup instructions with these
  steps: connect the listed MCP URL, complete OAuth with the supplied login,
  and run `get_status` to confirm the no-spend System advertiser before testing
  the remaining tools. The same login can be reopened for OpenAI, Anthropic,
  and Microsoft while its grant is active. It is not included in a download,
  repository, support ticket, or model-visible tool result; revoke it from
  either destination when directory review ends.

**Setup operations are shared by agents and the page.** An administrator can
complete the same guarded setup through the V2 Storefront MCP skill or
through these controls. Agents must make at most one mutation per turn and
obtain explicit confirmation for visibility changes, reviewer provisioning,
replacements/removals, and domain-ownership token changes. The printable
card download remains Page-only because its result contains PDF bytes rather
than a bounded model response.

| Operation                           | What it does                                                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `set_storefront_operator_domain`    | Saves the account's brand domain. See "Brand domain" above for what happens when this storefront intentionally uses a different, explicit domain: the account write still succeeds, but the storefront's displayed domain is left unchanged and the page says so rather than claiming a save that didn't reach the storefront.                                                                                             |
| `get_discovery_publication`         | Reads the current Discovery visibility, publish state, public listing domain status, and OpenAI Apps verification status.                                                                                                                                                                                                                                                                                                  |
| `publish_discovery`                 | Sets Discovery visibility to Apostra or Public and publishes it — Apostra corresponds to listing step 3's switch, Public to step 4 going live. Turning off the page's **Listed on Apostra marketplace** switch (with confirmation) sends `private`, publishing a new private revision that removes the storefront from Apostra and the public web. `private` remains a wire-level state action, not a distribution choice. |
| `save_discovery_hostname`           | Saves the storefront's own public listing domain (not available for the first-party storefront).                                                                                                                                                                                                                                                                                                                           |
| `activate_discovery_hostname`       | Verifies (or re-verifies) the storefront's public listing domain — the check inside listing step 4.                                                                                                                                                                                                                                                                                                                        |
| `download_discovery_printable_card` | **Page only.** Downloads the Listing card PDF from the current published storefront profile. Its PDF bytes never enter an agent conversation, and it does not order printing or fulfilment.                                                                                                                                                                                                                                |
| `update_discovery_openai_challenge` | Sets or removes the OpenAI Apps domain-ownership token, published from the ChatGPT destination.                                                                                                                                                                                                                                                                                                                            |
| `probe_discovery_openai_challenge`  | Re-checks OpenAI Apps verification status on demand.                                                                                                                                                                                                                                                                                                                                                                       |
| `get_chatgpt_app_config`            | Reads the ChatGPT destination's listing projection, reviewer-access status, and submission-bundle availability, in one call.                                                                                                                                                                                                                                                                                               |
| `save_chatgpt_app_listing`          | Saves the ChatGPT app's icon overrides. Name, subtitle, description, and required URLs project from the storefront listing; without overrides, directory/composer icons generate from the optional brand identity.                                                                                                                                                                                                         |
| `approve_chatgpt_app_listing`       | Records the owner's approval of the exact projected listing (by fingerprint). The page calls it when you choose **Download package**, after showing the values OpenAI will see — name, subtitle, description, website, and the directory category (Business for new registrations; an existing registration keeps its saved category); there is no separate checkbox.                                                      |
| `get_chatgpt_app_bundle_url`        | Returns a short-lived download URL after confirming the bundle is eligible. Opening that URL generates and downloads the current submission snapshot.                                                                                                                                                                                                                                                                      |
| `request_chatgpt_reviewer_sandbox`  | Requests storefront-owned, no-spend reviewer access for OpenAI. Provisioning and activation happen asynchronously.                                                                                                                                                                                                                                                                                                         |
| `revoke_chatgpt_reviewer_sandbox`   | Revokes an existing storefront reviewer grant when review access is no longer needed.                                                                                                                                                                                                                                                                                                                                      |

New setups have no ChatGPT app-specific hostname, app-store category
selector, or submission-review checkbox: Apostra sets the directory
category to Business for new registrations (an existing registration keeps
its saved category) and shows the value before download, OpenAI's review lives in
OpenAI's own portal, and domain ownership is proven once, on the storefront's
own domain, via `update_discovery_openai_challenge` /
`probe_discovery_openai_challenge` above. The four per-app tools
(`save_chatgpt_app_hostname`, `activate_chatgpt_app_hostname`,
`save_chatgpt_app_verification_token`, `probe_chatgpt_app_verification_token`)
remain live but legacy-only: they maintain an existing app that still runs on
its own per-app hostname until it moves to the public listing domain, and the
page shows those hostname and token controls for such apps. Owner approval of
the projected listing still happens, as part of **Download package**, and
covers exactly the values shown there — name, subtitle, description,
website, and the directory category shown there.

If a durable sync to the storefront fails after the account write succeeds,
the page reports that explicitly rather than showing a false "saved" —
Apostra is alerted on the failure and the seller is told to retry or contact
support.

## Rate cards — how you price

Your rate cards hold the current seller prices and packaging terms the
Merchandising Agent can use. Keep examples and materials in the
[Library](/v2/storefront/library), and keep acceptance policy in [AI Business
Rules](#business-rules--what-you-accept).

## `playbook` — how you sell

`save_playbook` has three independent halves you can send together or apart:
`content` (posture and packaging guidance, versioned), `pricing` (your
value-anchor facts), and `discounts` (your brand and operator rate-card
rules).

```json theme={null}
{
  "content": "## Posture\nLead with premium CTV before offering display discounts. Hold value on flagship inventory during Q4.\n\n## Packaging\nBundle CTV pre-roll with display when a brief mentions video.",
  "notes": "Initial playbook, drafted from the Q4 sales deck.",
  "pricing": {
    "currency": "USD",
    "facts": [
      {
        "id": "ctv-flagship",
        "label": "CTV flagship pre-roll",
        "appliesWhen": "Buyer requests CTV pre-roll on flagship shows",
        "pricingModel": "cpm",
        "targetPrice": 42,
        "floorPrice": 32,
        "strength": "hard_floor",
        "provenance": "2026 rate card, page 4"
      }
    ]
  }
}
```

```json theme={null}
{
  "playbook": {
    "active": {
      "version": 3,
      "content": "## Posture\nLead with premium CTV before offering display discounts. Hold value on flagship inventory during Q4.\n\n## Packaging\nBundle CTV pre-roll with display when a brief mentions video.",
      "notes": "Initial playbook, drafted from the Q4 sales deck.",
      "createdAt": "2026-07-28T15:04:00Z",
      "ownershipIssues": []
    },
    "pricing": {
      "currency": "USD",
      "facts": [
        {
          "id": "ctv-flagship",
          "label": "CTV flagship pre-roll",
          "appliesWhen": "Buyer requests CTV pre-roll on flagship shows",
          "pricing": {
            "pricingModel": "cpm",
            "currency": "USD",
            "targetPrice": 42,
            "floorPrice": 32
          },
          "strength": "hard_floor",
          "provenance": "2026 rate card, page 4"
        }
      ]
    }
  },
  "createdVersion": 3,
  "replacedVersion": 2
}
```

`ownershipIssues` identifies any line in the active guidance that duplicates
structured Playbook pricing, Buyer Discounts, Listing facts, or Business
Rules. `pricing.facts` **replaces the
whole list** — a fact you don't repeat in the request is removed, the same
"whole document" semantics as `content`.

### Discounts are per rule, not a whole list

`discounts` is the exception, and the difference is deliberate. A
[house discount](/v2/storefront/house-discounts/overview) is keyed by
`(houseDomain, scope)` and changes what a real buyer pays on their next buy,
so **a rule you don't mention is left exactly as it was**. Removing one means
naming it:

```json theme={null}
{
  "discounts": {
    "rules": [
      {
        "houseDomain": "nike.com",
        "scope": "brand",
        "discountPercent": 12,
        "notes": "2026 Nike-house rate"
      },
      { "houseDomain": "wpp.com", "scope": "operator", "discountPercent": 8 }
    ],
    "remove": [{ "houseDomain": "coke.com", "scope": "brand" }]
  }
}
```

* A rule for a `(houseDomain, scope)` pair that doesn't exist yet is created;
  one that does is updated. Sending a rule that already matches is a no-op and
  says so.
* Omitting `notes` keeps the note you already wrote. Send `null` to clear it.
* `remove` needs both halves of the key, because `nike.com` can carry a brand
  discount and an operator discount at once and they are different rules.
* Naming the same pair in `rules` and `remove` is rejected rather than
  resolved for you.

Each rule is applied independently. If one fails the response reports exactly
which one, keeps the rest, and tells you to retry only the failure — the rows
are individually keyed, so a retry repeats nothing that already succeeded.

Deciding whether to create or update a rule means knowing whether it already
exists, so if your current rules can only be read in part, that question has
no safe answer. In that case the affected declaration is **refused rather than
guessed**: you get "this pair could not be confirmed either way" instead of a
create that might collide with a live rule, or a "nothing to remove" that would
tell you a discount is gone while it keeps applying. Rules the partial read did
show are still updated and removed normally.

The `content` half of the same call is never rolled back for a `discounts`
failure, and vice versa: they are separate writes, and the response names what
landed.

### Which discount a buyer actually gets

The rules you author and the discount a buy receives are different things.
`search` will resolve one for you:

```json theme={null}
{ "kind": "house_discount", "filter": { "buyerDomain": "converse.com" } }
```

`results` is your authored rules, unchanged. `resolution` is derived: the
corporate `chain` the domain resolved up (`converse.com → nike.com`), the
hierarchy `coverage`, and the nearest rule that keys on each axis. It always
carries `caveats`, because a real buy resolves **its brand and its operator
separately** and takes the larger of the two — one domain's chain cannot
decide it. When the hierarchy can't be resolved, you get `unavailable` and
your authored rows rather than a confident "no discount applies".

## `business_rules` — what you accept

`save_business_rules` also has independent versioned policy sections and two
approval gates. Structured policy writes always send both sections, so an
omitted field can never erase the other section accidentally.

```json theme={null}
{
  "briefAcceptancePolicy": "We do not accept gambling, tobacco, or political advertisers.",
  "creativePolicy": "Fast-food creative requires review before it goes live.",
  "notes": "Adds a review requirement for fast-food advertisers."
}
```

```json theme={null}
{
  "businessRules": {
    "creativeApproval": "manual",
    "mediaBuyApproval": "manual",
    "policy": {
      "version": 5,
      "content": "We do not accept gambling, tobacco, or political advertisers.\n\n<!-- scope3:business-rules:creative-policy -->\n\nFast-food creative requires review before it goes live.",
      "notes": "Adds a review requirement for fast-food advertisers.",
      "createdAt": "2026-07-28T15:10:00Z"
    }
  },
  "createdVersion": 5,
  "replacedVersion": 4
}
```

### Turning off human review

Moving `creativeApproval` or `mediaBuyApproval` to `auto` needs
`acknowledgeNoHumanReview: true` on the same call:

```json theme={null}
{ "mediaBuyApproval": "auto", "acknowledgeNoHumanReview": true }
```

Leave it out and the save fails with a validation error rather than a silent
no-op:

```
Moving media buys to `auto` means nobody reviews them, and your
create-time Brief Acceptance screen is also skipped. Pass
acknowledgeNoHumanReview: true to proceed.
```

<Note>
  **`auto` removes human review; it does not remove the product-discovery policy
  boundary.** A confident policy conflict returns no products, while an
  unavailable qualifier fails open. Once a buyer passes discovery,
  `create_media_buy` preserves the established auto-forward contract. With
  `mediaBuyApproval: manual`, the submitted buy is evaluated again: clearly
  on-policy buys auto-forward, while uncertain results, evaluator errors, and
  deterministic hard findings queue for an operator. Live auto-rejection
  requires a separate seller opt-in or versioned rollout.
</Note>

## Every save is create-and-activate

`playbook.content` and `business_rules.content` are versioned and immutable.
Saving one **creates a new version and makes it active in the same call** —
the response's `createdVersion` and `replacedVersion` tell you what happened,
and there is no `activate` flag on the save. A version you save has full
effect immediately; there's no draft state where it exists but hasn't taken
effect.

### Rolling back: two routes, and the agent only has one of them

There is **no activation field or `activate_*` tool on `/mcp/v3`**, by design.
An agent rolls back the way it writes anything else — by saving the earlier
content again, which mints a new version carrying it. History stays
append-only, and the restored content is re-checked against the current
ownership rules on the way in.

The AI Business Rules Page has the other route: it lists every immutable version
and can **reactivate one directly**, behind a two-step confirmation, without
minting anything. Use it when you want the version count and history left
exactly as they are. Open it with `get_business_rules`, or on `/mcp/v3` with
`open_page` and `page: "business_rules"`.

Either way, decisions already recorded keep the policy they were made under.
Changing which version is active changes what happens next; it does not
retroactively re-judge a creative or media buy someone already approved.

`playbook` works the same way: save the earlier content again to roll back.

For the playbook that is a two-step journey, and both steps are reads you can
make:

<Steps>
  <Step title="Read the version you want back">
    `get({ kind: "playbook", version: 2 })` returns that version's full
    `content` plus a `restorable` flag.
  </Step>

  <Step title="Save that content again">
    `save_playbook({ content: "…" })` mints a new version carrying it, active
    immediately. Your history keeps both — nothing is overwritten.
  </Step>
</Steps>

`restorable: false` is worth reading before you try. Playbook content that
restates a price, a discount, a market, or an acceptance rule is rejected on
save because another canonical surface owns that fact, and an older version
written before that check can still contain one. Re-saving it puts it back through the
current validator, so a flagged version needs editing before it can go live
again. That is not a cost of the re-save route: the older `activate` call
re-runs the same validator and refuses a flagged version too, so the content
is unacceptable either way. Edit the flagged lines out and save what's left.

`save_playbook` and `save_business_rules` also each take unversioned halves
in the same call — `pricing` and `discounts` on the playbook, the approval
gates on business rules. The parts run in a fixed order — `content`, then
`pricing`, then `discounts` — and there is no transaction spanning them.

If a part fails, the response tells you exactly where the call stopped:

* Parts that already succeeded stay applied. A version that was created is
  live and is **not** rolled back, and the error still names `createdVersion`
  (and `replacedVersion`) so you know it exists before retrying.
* Parts that never ran are listed in `unattempted`, and the message names
  them. A `pricing` failure means your `discounts` were not read, created,
  changed or removed at all — so the retry guidance says to re-send
  `pricing` **and** `discounts`, and a `content` failure means neither of the
  other two was attempted and nothing is half-applied.
* Retry only what the response tells you to. Sending `content` again would
  mint a second, identical version rather than fixing anything.

## Approval routing: who reviews, as opposed to whether anyone reviews

The approval gates decide *whether* a human reviews. **Approval routing**
decides *which* human — the named users and roles who receive each kind of
review work, the channels they are notified on, and the reminder and
escalation clock. It is a third thing, on its own lifecycle: not versioned
like the acceptance policy, and not a gate.

Read it as an include on `business_rules`:

```json theme={null}
{ "kind": "business_rules", "include": ["approvalRouting"] }
```

```json theme={null}
{
  "approvalRouting": {
    "canManage": true,
    "managedBy": "open_approvals",
    "policies": [
      {
        "kind": "MEDIA_BUY",
        "primaryUsers": [
          { "id": "11", "name": "Ada Approver", "eligible": true }
        ],
        "fallbackUsers": [],
        "primaryRoles": ["ADMIN"],
        "fallbackRoles": [],
        "primaryChannels": ["in_app", "email"],
        "fallbackChannels": ["in_app"],
        "reminderAfterMinutes": 240,
        "escalateAfterMinutes": 1440,
        "version": 3
      }
    ],
    "eligibleRoles": [
      { "key": "ADMIN", "label": "Admins", "activeUserCount": 2 }
    ]
  }
}
```

An approval kind missing from `policies` is not unrouted — it falls back to
the default org-admin audience. An approver shown with `eligible: false` is
still named in the policy but no longer has an eligible role on the account,
which is worth cleaning up. Reading routing requires a storefront admin; a
non-admin session gets an `approvalRouting` entry under `unavailableIncludes`
explaining why, never an empty table that would read as "nobody reviews".

### Routing is changed on the Approvals Page, not by a tool

There is no routing field on `save_business_rules`, and passing one returns an
error naming the Page instead. Choosing an approver changes **who can act** on
your storefront, and that class of change is a human ceremony rather than
something an agent writes — the same rule that keeps credentials, access
grants, and payout destinations off the agent surface. Open the Page with
`open_approvals`; the routing editor lives there alongside the queues, and only
admins can save it.

Eligible users' email addresses are deliberately not returned to the agent.
An approver who has never set a name comes back as `"name": null` rather than
falling back to their address — they are still `eligible: true`, there is just
no name safe to show. The Page shows the full directory to the human choosing.

## Searching version history and discount rules

`playbook` and `business_rules` are singletons, so `search` doesn't search
*them*. Listing is a storefront projection rather than a searchable
noun. What **is** list-shaped is the version history and the playbook's
discount rules.

```json theme={null}
{ "kind": "playbook_version", "query": "CTV" }
```

```json theme={null}
{
  "sources": ["objects"],
  "objects": {
    "kind": "playbook_version",
    "total": 3,
    "matched": 1,
    "returned": 1,
    "truncated": false,
    "results": [
      {
        "version": 3,
        "isActive": true,
        "notes": "Initial playbook, drafted from the Q4 sales deck.",
        "createdAt": "2026-07-28T15:04:00Z"
      }
    ]
  }
}
```

`business_rules_version` works the same way over acceptance-policy history,
and `house_discount` lists the playbook's brand and operator rules.
Omit `kind` and `search` lists every list-shaped object it knows about,
`playbook_version`, `business_rules_version`, and `house_discount` included —
so a broad question about your setup never silently leaves your discounts out.

<CardGroup cols={2}>
  <Card title="Prepare inventory source inputs" icon="clipboard-check" href="/v2/setup/publisher-onboarding-starter-kit">
    Collect the evidence that powers Listing, Playbook, AI Business Rules, and
    source operations.
  </Card>

  <Card title="v3 Agent Surface" icon="flask" href="/v2/setup/v3-preview">
    The one-endpoint, noun-shaped surface these tools live on.
  </Card>

  <Card title="Merchandising" icon="store" href="/v2/concepts/merchandising">
    The three Pages you use today, and how they map to the platform.
  </Card>

  <Card title="AI Business Rules" icon="scale-balanced" href="/v2/concepts/ai-business-rules">
    How the pre-screen reads your policy, and the three verdicts it returns.
  </Card>

  <Card title="Glossary" icon="book-a" href="/v2/concepts/glossary">
    Component, Product, and the rest of the vocabulary these nouns build on.
  </Card>
</CardGroup>
