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

# Dappier Sales Agent MCP

The **Dappier Sales Agent MCP** is a remote Model Context Protocol (MCP) server that lets any AI agent or LLM client **discover Dappier advertising inventory, create Sponsored Conversations campaigns, manage Brand Agent creatives, and pull delivery metrics** — all without writing HTTP wrappers around the Dappier REST API.

It implements the [Advertising Context Protocol (AdCP)](https://adcontextprotocol.org/) sales-agent surface for Dappier and exposes **9 AI-callable tools** across two protocols: `media_buy` and `creative`.

The server is hosted at **[sales-agent.dappier.com](https://sales-agent.dappier.com)**.

## Watch the Video

If you prefer a visual walkthrough, check out the accompanying video below:

<iframe width="560" height="315" src="https://www.youtube.com/embed/5wbjj0jwp1A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />

# What is MCP?

If you've never seen MCP before, here's the mental model:

```mermaid theme={null}
flowchart LR
    A["<b>Your AI client / LLM</b><br/>Claude, Cursor,<br/>your own app, etc."]
    B["<b>Dappier Sales Agent MCP</b><br/>sales-agent.dappier.com"]
    C["<b>Dappier REST API</b><br/>campaigns, prompts,<br/>brand agents, etc."]

    A -- "JSON-RPC / HTTP" --> B
    B -- "tool results" --> A
    B --> C
```

* The **server** advertises tools (name + JSON schema + description).
* The **client** fetches that list and lets the LLM call any tool by name with arguments that match the schema.
* The server validates, executes, and returns a result the LLM can use to continue the conversation.

# What the Sales Agent MCP Server Does

Two AdCP protocols are supported:

| Protocol    | What it covers                                                                               |
| ----------- | -------------------------------------------------------------------------------------------- |
| `media_buy` | Discover products, create/update/cancel campaigns ("media buys"), pull delivery data.        |
| `creative`  | List creative formats, create/update Dappier Brand Agent creatives, list existing creatives. |

What you get as a developer:

1. An **AI-callable surface** for **Dappier Sponsored Conversations** — the branded prompt suggestions that appear inside publisher AI chat widgets across the Dappier network.
2. **Campaign lifecycle in a single tool call**: create a paused campaign with advertiser details, CTA, targeting, and optional sponsored prompts.
3. **Creative lifecycle**: attach a configurable **Dappier Brand Agent** (conversational AI creative) to a campaign via `build_creative`.
4. **Delivery reporting** over a date range.

What the server is **not**:

* It **does not quote prices** or sell impressions directly — pricing is handled offline by Dappier sales (`sales@dappier.com`).
* It **does not activate campaigns**. Every campaign is born `paused`; a Dappier reviewer must approve and a sales rep must complete Google Ad Manager (GAM) line-item setup before it serves.
* It **does not honor standard AdCP targeting** (geo, device, language, audience). Dappier targeting is `dappier_network` / `my_network` / `individual_agents`.

***

# Getting Started

## Base URLs and Endpoints

The server is hosted by Dappier. Connect using the standard MCP streamable-HTTP transport.

| Transport           | Path        | When to use                                                           |
| ------------------- | ----------- | --------------------------------------------------------------------- |
| **Streamable HTTP** | `POST /mcp` | All MCP clients (Claude.ai, Cursor, custom apps, `mcp-remote` proxy). |

Health / discovery endpoints (open, no auth):

| Path                           | Returns                                                      |
| ------------------------------ | ------------------------------------------------------------ |
| `GET /`                        | HTML landing page confirming the worker is up.               |
| `GET /.well-known/mcp.json`    | The AdCP server card (name, version, protocols, tool names). |
| `GET /.well-known/server.json` | Same server card (alias).                                    |

<Note>
  Use the deployed URL Dappier publishes — for example `https://sales-agent.dappier.com/mcp`. Confirm the current host with your Dappier contact.
</Note>

## Authentication

Every MCP request (except `/` and `/.well-known/*` discovery endpoints) requires a **Dappier API key**.

### Query parameter

```
https://<host>/mcp?apiKey=YOUR_DAPPIER_API_KEY
```

### HTTP header

```
dappier-api-key: YOUR_DAPPIER_API_KEY
```

If the key is missing you'll get an HTTP `401` with a plain-text body:

```
Authentication required

A Dappier API key is required to access this endpoint.

You can provide it in one of two ways:
  1. Query parameter:  ?apiKey=YOUR_DAPPIER_API_KEY
  2. Request header:   dappier-api-key: YOUR_DAPPIER_API_KEY

Don't have a key yet? Create one at:
  https://platform.dappier.com/profile/api-keys
```

### Getting an API key

Create Dappier API keys at [platform.dappier.com/profile/api-keys](https://platform.dappier.com/profile/api-keys). The key is used:

* As a Bearer token on every outbound call the server makes to `api.dappier.com`.
* To authorize the MCP session itself at the edge.

<Warning>
  Keep the key server-side or in a secret manager. Never expose it in browser code.
</Warning>

***

# Connecting from Common MCP Clients

## Claude Desktop (via `mcp-remote`)

Claude Desktop speaks MCP over stdio. To reach a remote HTTPS MCP server, proxy through `mcp-remote`.

Edit your Claude Desktop config (**Settings → Developer → Edit Config**):

```json theme={null}
{
  "mcpServers": {
    "dappier-sales-agent": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY"
      ]
    }
  }
}
```

Restart Claude Desktop — the 9 Dappier tools will appear in the tool picker.

## Claude.ai (Connectors / Remote MCP)

On [claude.ai](https://claude.ai), add a custom connector / remote MCP server pointing at `https://sales-agent.dappier.com/mcp`. Supply the API key via header (`dappier-api-key`) where the UI allows custom headers, or via `?apiKey=...` in the URL otherwise.

## Cursor

Add a remote MCP server in Cursor's MCP settings pointing at:

```
https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY
```

Cursor supports the streamable HTTP transport directly.

## Cloudflare AI Playground

Go to [playground.ai.cloudflare.com](https://playground.ai.cloudflare.com/) and enter this as the server URL:

```
https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY
```

## Custom Node.js Client

Any client built on `@modelcontextprotocol/sdk` can connect over streamable HTTP:

```ts TypeScript theme={null}
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://sales-agent.dappier.com/mcp"),
  {
    requestInit: {
      headers: { "dappier-api-key": process.env.DAPPIER_API_KEY! }
    }
  }
);

const client = new Client(
  { name: "my-app", version: "1.0.0" },
  { capabilities: {} }
);
await client.connect(transport);

const tools = await client.listTools();
console.log(tools);

const products = await client.callTool({
  name: "get_products",
  arguments: {
    buying_mode: "brief",
    brief: "Launch a new coffee brand in Q3"
  }
});
console.log(products);
```

## Anthropic SDK (Native MCP Connector)

The Anthropic Messages API can call a remote MCP server directly as a tool-use source — no separate MCP client SDK required:

```python Python theme={null}
client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    mcp_servers=[{
        "type": "url",
        "url": "https://sales-agent.dappier.com/mcp",
        "name": "dappier-sales-agent",
        "authorization_token": "YOUR_DAPPIER_API_KEY"  # sent as dappier-api-key
    }],
    messages=[{
        "role": "user",
        "content": "List my active Dappier campaigns."
    }]
)
```

***

# Server Discovery

`GET /.well-known/mcp.json` (and `/.well-known/server.json`) returns:

```json theme={null}
{
  "name": "com.dappier/sales-agent",
  "version": "1.0.0",
  "title": "Dappier Sales Agent",
  "description": "AdCP sales agent for Dappier's Sponsored Conversations network — discover inventory, create and manage branded-prompt campaigns across Dappier's publisher AI chat widgets.",
  "tools": [
    { "name": "get_adcp_capabilities" },
    { "name": "list_creative_formats" },
    { "name": "build_creative" },
    { "name": "list_creatives" },
    { "name": "get_products" },
    { "name": "create_media_buy" },
    { "name": "update_media_buy" },
    { "name": "get_media_buys" },
    { "name": "get_media_buy_delivery" }
  ],
  "_meta": {
    "adcontextprotocol.org": {
      "protocols_supported": ["media_buy", "creative"]
    }
  }
}
```

***

# Conventions

## ID prefixes

| Prefix | Meaning                           | Example          |
| ------ | --------------------------------- | ---------------- |
| `cp_`  | Dappier campaign / media buy id   | `cp_01HW9ZAB...` |
| `am_`  | Dappier Brand Agent / creative id | `am_01HW9CD...`  |
| `pm_`  | Sponsored prompt id               | `pm_01ABC...`    |

<Note>
  Schemas **reject** values that don't match the expected prefix — the server won't silently accept a mismatched id.
</Note>

## Response envelope

Every tool returns an MCP `CallToolResult` with two fields:

* `content[0]` — a `text` block containing pretty-printed JSON (for LLMs reading the text).
* `structuredContent` — the same payload as a machine-readable object (for programmatic clients).

Both contain the same data — use whichever matches your client.

## Error shape

Errors follow AdCP conventions:

```json theme={null}
{
  "errors": [
    { "code": "VALIDATION_ERROR", "message": "...", "field": "cta_link" }
  ]
}
```

For async-style tools, a `status: "failed"` wrapper is used:

```json theme={null}
{
  "status": "failed",
  "errors": [{ "code": "INVALID_REQUEST", "message": "..." }]
}
```

### Error code reference

| Code                  | Meaning                                                                      |
| --------------------- | ---------------------------------------------------------------------------- |
| `INVALID_REQUEST`     | Generic 400-level rejection on create.                                       |
| `VALIDATION_ERROR`    | Update with no fields, or 400-level rejection on update.                     |
| `INVALID_DATE_RANGE`  | Missing, malformed, or unsorted `start_date` / `end_date`.                   |
| `POLICY_VIOLATION`    | 401/403 (bad key, insufficient scope).                                       |
| `AUTH_REQUIRED`       | 401 on read tools.                                                           |
| `MEDIA_BUY_NOT_FOUND` | Unknown `cp_xxxx`.                                                           |
| `CREATIVE_NOT_FOUND`  | Unknown `am_xxxx`.                                                           |
| `INVALID_STATE`       | Campaign in a state that forbids the requested action.                       |
| `CREATIVE_ID_EXISTS`  | 409 conflict (duplicate creative attached).                                  |
| `CONTEXT_REQUIRED`    | Default-scope list returned zero; supply `media_buy_ids` or `status_filter`. |
| `VERSION_UNSUPPORTED` | `adcp_major_version` not supported.                                          |
| `INTERNAL_ERROR`      | Fallback.                                                                    |

## Context passthrough

Most tools accept a `context: Record<string, unknown>` field. Whatever you send is echoed back unchanged on the response — useful for correlating tool calls with your own session state.

## Typical campaign lifecycle

```
1. get_adcp_capabilities              — discover what the server supports
2. get_products                       — confirm Sponsored Conversations is the product
3. create_media_buy                   — submit the campaign (returns cp_xxxx, starts paused)
4. list_creative_formats              — find the dappier_brand_agent format id
5. build_creative (create mode)       — attach a Brand Agent to the cp_xxxx
6. [offline] Dappier sales sets up GAM, reviewer approves
7. update_media_buy { paused: false } — resume (may be rejected until sales approves)
8. get_media_buys                     — check operational state
9. get_media_buy_delivery             — pull impressions/clicks over a date range
```

***

# Tools Reference

## `get_adcp_capabilities`

**Purpose:** The first call a buyer should make. Tells you which AdCP protocols Dappier implements, which features inside those protocols are honored vs. ignored, the auth model, and which creative capabilities are available.

**Network behavior:** In-memory lookup. No outbound call. Returns instantly.

### Inputs (all optional)

| Field                | Type                                                                                                                  | Description                                                                                                                               |
| -------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `adcp_major_version` | integer                                                                                                               | AdCP major version the buyer's payloads conform to. If provided and unsupported, returns `VERSION_UNSUPPORTED`. Currently supported: `1`. |
| `protocols`          | array of `"media_buy" \| "signals" \| "governance" \| "sponsored_intelligence" \| "creative" \| "compliance_testing"` | Filter which protocols you want capability info for. Omit to get everything Dappier supports.                                             |

### Success response

```json theme={null}
{
  "adcp": { "major_versions": [1] },
  "supported_protocols": ["media_buy", "creative"],
  "account": {
    "supported_billing": ["operator"],
    "require_operator_auth": false,
    "required_for_products": false,
    "account_financials": false,
    "sandbox": false
  },
  "media_buy": {
    "features": {
      "inline_creative_management": false,
      "property_list_filtering": false,
      "content_standards": false,
      "audience_targeting": false
    },
    "execution": {
      "creative_specs": {
        "vast_versions": [],
        "mraid_versions": [],
        "vpaid": false,
        "simid": false
      },
      "targeting": { /* every geo/device/audience field is false */ }
    },
    "portfolio": {
      "publisher_domains": ["dappier.com"],
      "primary_channels": ["native"],
      "primary_countries": ["US"],
      "description": "Dappier operates a network of publishers running AI chat experiences...",
      "advertising_policies": "All campaigns require manual review..."
    }
  },
  "creative": {
    "has_creative_library": true,
    "supports_generation": false,
    "supports_transformation": false,
    "supports_compliance": false
  },
  "last_updated": "2026-04-10T00:00:00Z"
}
```

### Error response

```json theme={null}
{
  "errors": [
    { "code": "VERSION_UNSUPPORTED", "message": "AdCP major version 2 is not supported..." }
  ]
}
```

<Note>
  Targeting dimensions declared `false` **will return a validation error if sent** — they are not silently dropped. `media_buy` and `creative` are the only protocols you'll see.
</Note>

***

## `list_creative_formats`

**Purpose:** Discover the creative formats supported by the Dappier sales agent. Call this before `build_creative`.

**Network behavior:** In-memory. Instant.

### Inputs

All inputs are optional and all are **ignored in v1** — accepted for AdCP forward-compat:

| Field                                                | Type                                         | Notes    |
| ---------------------------------------------------- | -------------------------------------------- | -------- |
| `format_ids`                                         | array of `{ agent_url: string, id: string }` | Ignored. |
| `asset_types`                                        | `string[]`                                   | Ignored. |
| `max_width`, `max_height`, `min_width`, `min_height` | integer                                      | Ignored. |
| `is_responsive`                                      | boolean                                      | Ignored. |
| `name_search`                                        | string                                       | Ignored. |
| `wcag_level`                                         | `"A" \| "AA" \| "AAA"`                       | Ignored. |
| `pagination.max_results`, `pagination.cursor`        |                                              | Ignored. |

### Success response

Returns a single format descriptor for `dappier_brand_agent` (the only creative format Dappier exposes). The `format_id.agent_url` returned here is the **exact value** you must pass into `build_creative.target_format_id`.

<Note>
  Call this tool to pick up the exact `agent_url` string — **don't hard-code it**.
</Note>

***

## `build_creative`

**Purpose:** Create a new Dappier Brand Agent (conversational AI creative) and attach it to a campaign, or update an existing one.

A **Brand Agent** is a configurable chat experience branded for an advertiser: name, description, optional persona, optional single knowledge source (RSS feed or webpage), and optional widget overrides (logo, colors, welcome copy, theme).

### Two modes

| Mode       | How to trigger                   | Effect                                                                                                                                                                           |
| ---------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CREATE** | Omit `creative_id`               | Creates a new Brand Agent, attaches it to `media_buy_id`. Requires `creative_manifest.assets.name` and `...description`. The campaign must not already have a creative attached. |
| **UPDATE** | Supply `creative_id` (`am_xxxx`) | PATCH semantics — only the fields you send change. `source_url` and `source_type` must be supplied together or not at all.                                                       |

### Inputs

| Field                                          | Type                                            | Required           | Notes                                                                        |
| ---------------------------------------------- | ----------------------------------------------- | ------------------ | ---------------------------------------------------------------------------- |
| `target_format_id`                             | `{ agent_url: URL, id: "dappier_brand_agent" }` | yes                | Must be `dappier_brand_agent`. Get `agent_url` from `list_creative_formats`. |
| `media_buy_id`                                 | `cp_xxxx` string                                | yes                | Campaign to attach the Brand Agent to.                                       |
| `creative_id`                                  | `am_xxxx` string                                | no                 | Present → UPDATE. Omit → CREATE.                                             |
| `creative_manifest.format_id`                  | `{ agent_url, id: "dappier_brand_agent" }`      | yes                | Must equal `target_format_id`.                                               |
| `creative_manifest.assets.name`                | string ≤120                                     | required on CREATE | Shown as "Ask \{name}".                                                      |
| `creative_manifest.assets.description`         | string ≤500                                     | required on CREATE | Brand description used inside the conversation.                              |
| `creative_manifest.assets.persona`             | string                                          | no                 | Free-form tone instruction.                                                  |
| `creative_manifest.assets.source_url`          | URL                                             | no                 | RSS feed or webpage. Must pair with `source_type`.                           |
| `creative_manifest.assets.source_type`         | `"rss" \| "webpage"`                            | no                 | Must pair with `source_url`.                                                 |
| `creative_manifest.assets.logo_url`            | URL                                             | no                 | Widget logo override.                                                        |
| `creative_manifest.assets.primary_color`       | string                                          | no                 | Hex color, e.g. `#8353E2`.                                                   |
| `creative_manifest.assets.welcome_title`       | string                                          | no                 | Title above the Ask-AI widget.                                               |
| `creative_manifest.assets.welcome_description` | string                                          | no                 | Subtitle above the widget.                                                   |
| `creative_manifest.assets.placeholder_text`    | string                                          | no                 | Placeholder inside the input box.                                            |
| `creative_manifest.assets.theme_mode`          | `"light" \| "dark"`                             | no                 | Widget theme.                                                                |
| `context`                                      | `Record<string, unknown>`                       | no                 | Echoed back.                                                                 |

### Success response

```json theme={null}
{
  "creative_id": "am_...",
  "media_buy_id": "cp_...",
  "widget_id": "...",
  "target_format_id": {
    "agent_url": "...",
    "id": "dappier_brand_agent"
  }
}
```

### Example — CREATE

```json theme={null}
{
  "target_format_id": {
    "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
    "id": "dappier_brand_agent"
  },
  "media_buy_id": "cp_01HW9ZAB...",
  "creative_manifest": {
    "format_id": {
      "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
      "id": "dappier_brand_agent"
    },
    "assets": {
      "name": "Acme Pet Food",
      "description": "Premium, science-backed nutrition for dogs and cats.",
      "persona": "Friendly, expert, concise.",
      "source_url": "https://acmepetfood.com/blog/feed.xml",
      "source_type": "rss",
      "primary_color": "#8353E2",
      "welcome_title": "Ask Acme Pet Food",
      "theme_mode": "light"
    }
  }
}
```

### Example — UPDATE (change logo only)

```json theme={null}
{
  "target_format_id": {
    "agent_url": "...",
    "id": "dappier_brand_agent"
  },
  "media_buy_id": "cp_01HW9ZAB...",
  "creative_id": "am_01HW9CD...",
  "creative_manifest": {
    "format_id": { "agent_url": "...", "id": "dappier_brand_agent" },
    "assets": {
      "logo_url": "https://cdn.acme.com/logo-new.png"
    }
  }
}
```

***

## `list_creatives`

**Purpose:** Browse the tenant's Brand Agents, or look up specific ones.

### Three filter paths

Mutually exclusive on the primary id filters:

| Path                 | How to trigger          | Behavior                                                                                                         |
| -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **By creative ids**  | `filters.creative_ids`  | Look up specific Brand Agents. Missing ids → `CREATIVE_NOT_FOUND` entries in `errors[]`. Pagination ignored.     |
| **By media buy ids** | `filters.media_buy_ids` | List Brand Agents attached to specific campaigns. Missing campaigns → `MEDIA_BUY_NOT_FOUND`. Pagination ignored. |
| **Default**          | omit both               | Paginated list of every tenant Brand Agent.                                                                      |

<Note>
  If both `creative_ids` and `media_buy_ids` are supplied, `creative_ids` wins.
</Note>

`filters.name_contains` (case-insensitive) and `filters.statuses` (`approved` / `pending_review` / `archived`) narrow any of the three paths.

### Inputs

| Field                    | Type                                               | Notes                         |
| ------------------------ | -------------------------------------------------- | ----------------------------- |
| `filters.creative_ids`   | `am_xxxx[]`                                        | Lookup by id.                 |
| `filters.media_buy_ids`  | `cp_xxxx[]`                                        | Lookup by campaign.           |
| `filters.name_contains`  | string                                             | Case-insensitive substring.   |
| `filters.statuses`       | `("approved" \| "pending_review" \| "archived")[]` |                               |
| `pagination.max_results` | int 1-100 (default 50)                             | Honored only on default path. |
| `pagination.cursor`      | string                                             | Opaque — round-trip verbatim. |
| `context`                | object                                             | Echoed back.                  |

***

## `get_products`

**Purpose:** Discover Dappier's advertising inventory. Always returns the single **Sponsored Conversations** product (or an empty list if your filters exclude it).

**Network behavior:** In-memory. No API call.

### Inputs

| Field                   | Type                                 | Required                               | Notes                                                             |
| ----------------------- | ------------------------------------ | -------------------------------------- | ----------------------------------------------------------------- |
| `buying_mode`           | `"brief" \| "wholesale" \| "refine"` | yes                                    | How you want results selected.                                    |
| `brief`                 | string                               | required iff `buying_mode === "brief"` | Natural-language campaign description. Must be omitted otherwise. |
| `brand.domain`          | string                               | no                                     | E.g. `"acmepetfood.com"`.                                         |
| `brand.brand_id`        | string                               | no                                     | Optional stable id.                                               |
| `filters.delivery_type` | `"guaranteed" \| "non_guaranteed"`   | no                                     | Dappier only offers `non_guaranteed`; anything else returns `[]`. |
| `filters.channels`      | `string[]`                           | no                                     | Must include `"native"` (or be omitted) for the product to match. |
| `filters.format_ids`    | `{ agent_url, id }[]`                | no                                     | Must include Dappier's format to match.                           |

### Success response

```json theme={null}
{
  "products": [{
    "product_id": "sponsored_conversations",
    "name": "Sponsored Conversations",
    "description": "A branded prompt suggestion...",
    "publisher_properties": [
      { "publisher_domain": "dappier.com", "property_tags": ["dappier_network"] }
    ],
    "format_ids": [{
      "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
      "id": "sponsored_prompt_standard"
    }],
    "delivery_type": "non_guaranteed",
    "pricing_options": [{
      "pricing_option_id": "contact_sales",
      "pricing_model": "flat_rate",
      "currency": "USD",
      "min_spend_per_package": 0
    }],
    "brief_relevance": "Strong fit — ..."
  }]
}
```

<Note>
  `brief_relevance` is present only when `brief` was supplied.
</Note>

***

## `create_media_buy`

**Purpose:** Create a sponsored campaign on the Dappier network. **Campaign is born `paused`.** It will not serve until:

1. Dappier sales sets up the GAM line item offline.
2. A Dappier reviewer approves it.

You can optionally pass `sponsored_prompts` to create the clickable prompt content atomically with the campaign.

### Inputs

| Field                                      | Type                                                           | Required  | Notes                                                                                                                                       |
| ------------------------------------------ | -------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `brand.name`                               | string                                                         | yes       | → Dappier `details.advertiser_name`.                                                                                                        |
| `brand.domain`                             | string                                                         | no        |                                                                                                                                             |
| `buyer_ref`                                | string                                                         | no        | Your correlation id, echoed in the response.                                                                                                |
| `packages`                                 | `Package[]`                                                    | defaulted | Defaults to a single `sponsored_conversations` package. Only set it to pass `targeting_overlay.product_ids` for individual-agent targeting. |
| `packages[].product_id`                    | literal `"sponsored_conversations"`                            | defaulted | Only allowed value.                                                                                                                         |
| `packages[].pricing_option_id`             | literal `"contact_sales"`                                      | defaulted | Only allowed value.                                                                                                                         |
| `packages[].format_ids[]`                  | `{ agent_url, id: "sponsored_prompt_standard" }`               | defaulted |                                                                                                                                             |
| `packages[].targeting_overlay.product_ids` | `string[]`                                                     | no        | `am_xxxx` agent ids. Only honored when `targeting_type === "individual_agents"`.                                                            |
| `start_time`                               | ISO 8601 datetime                                              | no        |                                                                                                                                             |
| `end_time`                                 | ISO 8601 datetime                                              | no        |                                                                                                                                             |
| `campaign_name`                            | string                                                         | **yes**   | → Dappier `details.name`.                                                                                                                   |
| `cta_link`                                 | URL                                                            | **yes**   | Where clicks land. **Must be supplied by the user — never infer from brand name / domain.**                                                 |
| `cta_button_text`                          | string                                                         | **yes**   | E.g. `"Shop now"`, `"Learn more"`. **Must be supplied by the user — never invent.**                                                         |
| `targeting_type`                           | `"dappier_network" \| "my_network" \| "individual_agents"`     | defaulted | Default: `dappier_network`.                                                                                                                 |
| `sponsored_prompts[]`                      | `{ prompt_text (≤200), thumbnail?, is_active (default true) }` | no        | Any caller-supplied `prompt_id` is silently dropped on create — the server assigns new `pm_xxxx` ids.                                       |
| `context`                                  | object                                                         | no        | Echoed back.                                                                                                                                |

### Success response

```json theme={null}
{
  "status": "submitted",
  "media_buy_id": "cp_01HW9ZAB...",
  "task_id": "cp_01HW9ZAB...",
  "buyer_ref": "...",
  "message": "Campaign created in paused state. Dappier sales will contact you to complete GAM setup and activate the campaign.",
  "confirmed_at": "2026-04-18T14:00:00Z",
  "packages": [{
    "package_id": "cp_01HW9ZAB...",
    "product_id": "sponsored_conversations",
    "status": "pending_activation"
  }],
  "context": { /* your context, echoed */ }
}
```

### Error response

```json theme={null}
{
  "status": "failed",
  "errors": [{ "code": "INVALID_REQUEST", "message": "..." }]
}
```

**Error code mapping:** 400 → `INVALID_REQUEST`, 401/403 → `POLICY_VIOLATION`, 409 → `CREATIVE_ID_EXISTS`, else → `INTERNAL_ERROR`.

### Example

```json theme={null}
{
  "brand": { "name": "Acme Pet Food", "domain": "acmepetfood.com" },
  "campaign_name": "Q3 Puppy Launch",
  "cta_link": "https://acmepetfood.com/puppy",
  "cta_button_text": "Shop the launch",
  "targeting_type": "dappier_network",
  "start_time": "2026-07-01T00:00:00Z",
  "end_time":   "2026-09-30T23:59:59Z",
  "sponsored_prompts": [
    { "prompt_text": "Best food for new puppies?", "is_active": true },
    { "prompt_text": "Is grain-free food worth it?" }
  ]
}
```

***

## `update_media_buy`

**Purpose:** Modify an existing Dappier campaign. **PATCH semantics** — omitted fields are preserved. **Atomic** — either all changes apply or none do.

The handler routes to one of three backend operations based on what you send:

| Request shape                               | Backend action                                                       |
| ------------------------------------------- | -------------------------------------------------------------------- |
| `canceled: true` (any other field optional) | Soft-delete the campaign. **Irreversible.** Overrides other changes. |
| Only `paused` set, no other update fields   | Status-only flip (pause/resume).                                     |
| Any update field (optionally plus `paused`) | Full update (all provided fields).                                   |

### Inputs

All optional except `media_buy_id`:

| Field                                                                          | Type                                                       | Notes                                                                                                  |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `media_buy_id`                                                                 | `cp_xxxx`                                                  | **Required.**                                                                                          |
| `paused`                                                                       | boolean                                                    | `true` = pause, `false` = resume. Resume may be rejected until Dappier sales approves.                 |
| `canceled`                                                                     | literal `true`                                             | Soft-delete. Irreversible.                                                                             |
| `cancellation_reason`                                                          | string                                                     | Echoed in the response message.                                                                        |
| `start_time`, `end_time`                                                       | ISO 8601                                                   | New flight dates.                                                                                      |
| `campaign_name`, `advertiser_name`, `brand_description`, `contextual_keywords` | string                                                     | Map to `details.*`.                                                                                    |
| `targeting_type`                                                               | `"dappier_network" \| "my_network" \| "individual_agents"` |                                                                                                        |
| `product_ids`                                                                  | `am_xxxx[]`                                                | Replaces `details.targeting.agent_list`.                                                               |
| `cta_link`                                                                     | URL                                                        | **Only include if the user explicitly asked to change it.** Otherwise omit (existing value preserved). |
| `cta_button_text`                                                              | string                                                     | **Only include if the user explicitly asked.**                                                         |
| `custom_followup_prompts`                                                      | `string[]` (max 3)                                         | Replaces existing list.                                                                                |
| `sponsored_prompts[]`                                                          | `{ prompt_id?, prompt_text, thumbnail?, is_active }`       | Full-replacement semantics — see below.                                                                |
| `context`                                                                      | object                                                     | Echoed back.                                                                                           |

### Sponsored-prompts replacement semantics

Pass the **full desired set** after the update:

* Entries **with** `prompt_id` (`pm_xxxx`) → updated.
* Entries **without** `prompt_id` → newly created.
* Existing prompts **not present** in the array → soft-deleted.

### Success response

```json theme={null}
{
  "status": "completed",
  "media_buy_id": "cp_01HW9...",
  "implementation_date": "2026-04-18T14:02:11Z",
  "affected_packages": [],
  "context": { /* echoed */ }
}
```

### Resume-pending special case

If you send `paused: false` and the backend rejects with `INVALID_STATE`, `NOT_APPROVED`, or `PENDING_APPROVAL`, the tool returns `status: "submitted"` (**not an error**) with the message:

> Resume requires Dappier sales to complete GAM setup and a reviewer to approve. The campaign remains paused.

### Validation error — empty request

If you send no `canceled`, no `paused`, and no update fields:

```json theme={null}
{
  "status": "failed",
  "errors": [{ "code": "VALIDATION_ERROR", "message": "..." }]
}
```

### Examples

**Pause a campaign:**

```json theme={null}
{ "media_buy_id": "cp_01HW9...", "paused": true }
```

**Cancel with a reason:**

```json theme={null}
{
  "media_buy_id": "cp_01HW9...",
  "canceled": true,
  "cancellation_reason": "Client request"
}
```

**Replace the prompt list:**

```json theme={null}
{
  "media_buy_id": "cp_01HW9...",
  "sponsored_prompts": [
    { "prompt_id": "pm_01ABC...", "prompt_text": "Updated text", "is_active": true },
    { "prompt_text": "New prompt", "is_active": true }
  ]
}
```

<Warning>
  Existing prompts not listed in the array are soft-deleted.
</Warning>

***

## `get_media_buys`

**Purpose:** Retrieve the current **operational state** of Dappier campaigns — configuration, sponsored-prompt approval status, and which campaigns are waiting on Dappier sales / GAM setup.

Use this for *"what's the current state of my campaigns?"*. For performance over a date range, use `get_media_buy_delivery` instead.

### Inputs (all optional)

| Field                    | Type                                                                                                                          | Notes                                                                                               |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `media_buy_ids`          | `cp_xxxx[]`                                                                                                                   | Fetch specific campaigns in parallel. When set, no implicit status filter is applied.               |
| `status_filter`          | one of / array of `"pending_creatives" \| "pending_start" \| "active" \| "paused" \| "completed" \| "rejected" \| "canceled"` | Defaults to `["active"]` when neither `media_buy_ids` nor `status_filter` is provided.              |
| `include_snapshot`       | boolean (default `false`)                                                                                                     | Accepted for AdCP compliance. Always returns `snapshot_unavailable_reason: "SNAPSHOT_UNSUPPORTED"`. |
| `pagination.max_results` | int 1-100 (default 50)                                                                                                        |                                                                                                     |
| `pagination.cursor`      | string                                                                                                                        | Opaque. Currently encoded as `page:<n>`.                                                            |
| `context`                | object                                                                                                                        | Echoed back.                                                                                        |

### Success response

```json theme={null}
{
  "media_buys": [
    {
      "media_buy_id": "cp_01HW9...",
      "status": "pending_creatives",
      "currency": "USD",
      "total_budget": null,
      "creative_deadline": null,
      "confirmed_at": "2026-04-18T14:00:00Z",
      "revision": 1,
      "valid_actions": ["pause", "cancel", "update_budget", "update_dates", "update_media_buy"],
      "packages": [{
        "package_id": "cp_01HW9...",
        "paused": false,
        "canceled": false,
        "creative_approvals": [
          {
            "creative_id": "pm_01ABC...",
            "approval_status": "approved",
            "rejection_reason": "..."
          }
        ],
        "format_ids_pending": [],
        "snapshot_unavailable_reason": "SNAPSHOT_UNSUPPORTED"
      }],
      "cancellation": null
    }
  ],
  "pagination": { "has_more": false },
  "errors": []
}
```

### Status derivation rules

* `is_deleted === true` → `canceled`
* else `status === "active"` → `active`
* else if creative\_ids exist → `paused`
* else → `pending_creatives`

### Valid actions by status

| Status              | Actions                                                                 |
| ------------------- | ----------------------------------------------------------------------- |
| `pending_creatives` | `cancel`, `update_media_buy`                                            |
| `active`            | `pause`, `cancel`, `update_budget`, `update_dates`, `update_media_buy`  |
| `paused`            | `resume`, `cancel`, `update_budget`, `update_dates`, `update_media_buy` |
| `canceled`          | —                                                                       |

### Empty-result nudge

If you pass neither `media_buy_ids` nor `status_filter`, the default filter is `["active"]`. If that produces no campaigns, the response includes:

```json theme={null}
{
  "errors": [{
    "code": "CONTEXT_REQUIRED",
    "message": "No campaigns matched the default scope..."
  }]
}
```

***

## `get_media_buy_delivery`

**Purpose:** Retrieve delivery metrics (impressions, clicks, etc.) for Dappier campaigns over a date range or campaign lifetime.

Use this for *"how did my campaigns perform over a period?"*. For current state, use `get_media_buys`.

### Inputs

| Field           | Type                                                                                              | Notes                                                                                              |
| --------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `media_buy_ids` | `cp_xxxx[]`                                                                                       | When set, no implicit status filter is applied.                                                    |
| `status_filter` | one of / array of `"pending_creatives" \| "pending_start" \| "active" \| "paused" \| "completed"` | Defaults to `["active"]` server-side when neither `media_buy_ids` nor `status_filter` is provided. |
| `start_date`    | `YYYY-MM-DD`                                                                                      | Inclusive. Must be paired with `end_date`.                                                         |
| `end_date`      | `YYYY-MM-DD`                                                                                      | Exclusive. Must be paired with `start_date`.                                                       |
| `context`       | object                                                                                            | Echoed back.                                                                                       |

### Date range rules

* Send **both** dates or **neither**.
* Both must match `YYYY-MM-DD`.
* `start_date < end_date` strictly.
* Violations return `INVALID_DATE_RANGE` with `field` set to the offender — no backend call is made.

**Omitting both dates** returns lifetime-to-date data.

### Unsupported in v1

* Reporting dimensions (geo, device, audience, placement).
* Spend, ROAS, CPM, conversion value — pricing is handled offline in GAM, not tracked in Dappier.

### Success response

Whatever the backend returns for the AdCP delivery shape is passed through verbatim, plus your `context` echo. Expect at minimum per-campaign impressions/clicks and a `reporting_period` object.

### Error response

```json theme={null}
{
  "errors": [{
    "code": "INVALID_DATE_RANGE",
    "message": "end_date must be after start_date",
    "field": "end_date"
  }]
}
```

**Error mapping:** backend `code` / `error_code` passed through if provided, else 404 → `MEDIA_BUY_NOT_FOUND`, 401 → `AUTH_REQUIRED`, 400 → `INVALID_DATE_RANGE`, else → `INTERNAL_ERROR`.

***

# End-to-End Recipes

## Launch a campaign (minimum viable flow)

```
1. get_adcp_capabilities
   → confirm media_buy + creative protocols are available.

2. get_products { buying_mode: "brief", brief: "launch new coffee brand in Q3" }
   → confirm Sponsored Conversations is the product.

3. create_media_buy {
     brand: { name, domain },
     campaign_name: "...",
     cta_link: "<user-supplied>",
     cta_button_text: "<user-supplied>",
     sponsored_prompts: [ { prompt_text }, ... ]   // optional
   }
   → returns cp_xxxx (paused).

4. list_creative_formats
   → pick up dappier_brand_agent's agent_url.

5. build_creative {
     target_format_id: { agent_url, id: "dappier_brand_agent" },
     media_buy_id: "cp_xxxx",
     creative_manifest: {
       format_id: { ... },
       assets: { name, description, persona?, source_url?, source_type?, ... }
     }
   }
   → returns am_xxxx creative + widget_id.

6. [Dappier sales + reviewer complete setup offline]

7. update_media_buy { media_buy_id: "cp_xxxx", paused: false }
   → campaign goes live (or comes back as status: "submitted" if still pending approval).
```

## What's happening with my campaigns right now?

```json theme={null}
{ "status_filter": ["active", "paused", "pending_creatives"] }
```

Or for specific campaigns:

```json theme={null}
{ "media_buy_ids": ["cp_...", "cp_..."] }
```

## How did last month perform?

```json theme={null}
{
  "media_buy_ids": ["cp_..."],
  "start_date": "2026-03-01",
  "end_date":   "2026-04-01"
}
```

## Edit the prompts on an existing campaign

```json theme={null}
{
  "media_buy_id": "cp_...",
  "sponsored_prompts": [
    { "prompt_id": "pm_EXISTING_ONE", "prompt_text": "Updated text", "is_active": true },
    { "prompt_text": "Brand-new prompt" }
  ]
}
```

<Note>
  Any pre-existing prompts not listed above are soft-deleted.
</Note>

## Attach a different Brand Agent to a campaign

Call `build_creative` in **UPDATE mode** with the existing `creative_id`. Only send the asset fields you want to change — the rest are preserved.

***

# Guardrails the Server Enforces

These are quiet-but-strict rules callers often trip over.

| Rule                                                                                                                                                           | Where                                  |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| Never infer `cta_link` or `cta_button_text` from the brand. **Ask the user.** The schema descriptions say so explicitly and the backend has no way to recover. | `create_media_buy`, `update_media_buy` |
| `creative_id` starts with `am_`, `media_buy_id` starts with `cp_`, prompt ids with `pm_`.                                                                      | Everywhere                             |
| `source_url` and `source_type` must be supplied together.                                                                                                      | `build_creative`                       |
| `brief` is required iff `buying_mode === "brief"`; forbidden otherwise.                                                                                        | `get_products`                         |
| `start_date` and `end_date` must both be present or both absent; `start_date < end_date`.                                                                      | `get_media_buy_delivery`               |
| `update_media_buy` with no fields at all returns `VALIDATION_ERROR`.                                                                                           | `update_media_buy`                     |
| `custom_followup_prompts` max 3 items.                                                                                                                         | `update_media_buy`                     |
| Any targeting dimension declared `false` in capabilities will reject rather than silently drop.                                                                | `create_media_buy`, `update_media_buy` |

***

# FAQ

<AccordionGroup>
  <Accordion title="Do I need to call get_adcp_capabilities on every request?">
    No. Call it once per session to discover the surface, then cache the result.
  </Accordion>

  <Accordion title="Why is my new campaign still paused an hour later?">
    Every Dappier campaign is born paused and requires (a) Dappier sales to configure the GAM line item offline and (b) a reviewer to approve. Calling `update_media_buy { paused: false }` before those steps complete returns `status: "submitted"` with an explanatory message, not an error.
  </Accordion>

  <Accordion title="Can I set a budget?">
    Pricing is offline. Budget / CPM / ROAS fields are not honored. `pricing_option_id` is always `"contact_sales"`.
  </Accordion>

  <Accordion title="Can I target by geo / device / audience?">
    No. Dappier targeting is `dappier_network` (whole network), `my_network` (your own publishers), or `individual_agents` (list of `am_xxxx` ids).
  </Accordion>

  <Accordion title="What happens if I pass extra fields the server doesn't understand?">
    Fields defined by AdCP but unused by Dappier (`idempotency_key`, `budget`, `include_snapshot`, `include_history`, `reporting_dimensions`, format-filter arguments, etc.) are accepted for forward compatibility but ignored. Fields that are targeting dimensions Dappier doesn't support will cause validation errors.
  </Accordion>

  <Accordion title="How do I correlate a tool call with my own request id?">
    Pass `context: { your_request_id: "..." }` on any tool — it's echoed back unchanged.
  </Accordion>

  <Accordion title="Is the cursor format stable?">
    `get_media_buys` currently uses `page:<n>` cursors. Treat it as opaque — don't parse or construct it yourself.
  </Accordion>

  <Accordion title="Can I create multiple creatives on one campaign?">
    No. On CREATE, the target campaign must not already have a creative attached. Use UPDATE mode to modify the existing Brand Agent.
  </Accordion>
</AccordionGroup>

***

# Glossary

| Term                        | Meaning                                                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **AdCP**                    | Advertising Context Protocol — an open spec for AI-callable ad-platform tools.                                                       |
| **MCP**                     | Model Context Protocol — the transport/framing standard this server speaks.                                                          |
| **Media buy**               | An AdCP term for a campaign. One Dappier campaign = one media buy.                                                                   |
| **Package**                 | An AdCP subdivision of a media buy. Dappier has no native package model — each campaign is returned with a single synthetic package. |
| **Sponsored Conversations** | Dappier's only product. A branded prompt pill that opens into a full Brand Agent conversation.                                       |
| **Brand Agent**             | The conversational AI creative attached to a campaign. Id prefix `am_`.                                                              |
| **Sponsored prompt**        | The clickable prompt text shown to users. Id prefix `pm_`.                                                                           |
| **GAM**                     | Google Ad Manager — where Dappier sales configures line items offline before a campaign can serve.                                   |
| **Dappier network**         | The set of publishers running Dappier AI chat widgets.                                                                               |

***

# Conclusion

The **Dappier Sales Agent MCP** gives AI agents a complete, AI-callable surface over the Dappier Sponsored Conversations network — campaign creation, Brand Agent creatives, and delivery reporting — while keeping pricing and activation in the hands of Dappier's sales team.

🔗 Explore further:

* [Dappier Developers](https://dappier.com/developers/)
* [Dappier Platform](https://platform.dappier.com) — create your API key
* [AdCP Specification](https://adcontextprotocol.org/)
* [Model Context Protocol](https://modelcontextprotocol.io/)
* Questions on pricing or activation? Contact [sales@dappier.com](mailto:sales@dappier.com)
