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

# Sandbox mode

> AdCP sandbox mode — test product discovery, campaign creation, creatives, and delivery with simulated data. No real spend or production side effects.

## Overview

Sandbox mode lets buyers test the full media buying lifecycle — discovery, campaign creation, creatives, and delivery — without real platform calls or spending real money. Responses contain simulated but realistic data.

Sandbox is **account-level**, not per-request. Once a request references a sandbox account, the entire request is treated as sandbox. This eliminates the risk of accidentally mixing real and test traffic in a multi-step flow.

## Capabilities discovery

Sellers declare sandbox support in `get_adcp_capabilities`:

```json theme={null}
{
  "account": {
    "sandbox": true
  }
}
```

Check this before using sandbox mode. If `account.sandbox` is not declared or is `false`, the seller does not support sandbox.

## Two paths to sandbox

How you enter sandbox mode depends on the seller's account model (`require_operator_auth`). The two paths are completely different — make sure you follow the right one.

### Buyer-declared accounts (`require_operator_auth: false`)

The seller trusts the agent and does not require per-operator authentication. Sandbox is part of the **natural key** — the same brand/operator pair can have both a production and a sandbox account, distinguished by `sandbox: true`.

**Setup:** Declare a sandbox account via `sync_accounts` with `sandbox: true` on the account entry:

```json theme={null}
// sync_accounts — declare a sandbox account
{
  "accounts": [{
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "billing": "operator",
    "sandbox": true
  }]
}
```

**Usage:** Reference the sandbox account by natural key with `sandbox: true` on every request:

```json theme={null}
// get_products — implicit sandbox
{
  "account": {
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "sandbox": true
  },
  "brief": "Premium CTV inventory for Q2 campaign"
}
```

### Account-id namespaces (`require_operator_auth: true`)

The seller requires each operator to authenticate directly. Sandbox accounts are **pre-existing test accounts on the seller's platform** — think Stripe test mode, Google Ads sandbox accounts, or Snap test advertiser accounts. You do not create them; you discover them.

**Setup:** Discover sandbox accounts via `list_accounts` with the `sandbox: true` filter:

```json theme={null}
// list_accounts — find sandbox accounts
{
  "sandbox": true
}
```

The seller returns pre-existing test accounts:

```json theme={null}
{
  "accounts": [{
    "account_id": "acct_sandbox_acme_001",
    "name": "Acme Test Account",
    "status": "active",
    "sandbox": true
  }]
}
```

**Usage:** Reference the sandbox account by `account_id` on every request:

```json theme={null}
// get_products — explicit sandbox
{
  "account": { "account_id": "acct_sandbox_acme_001" },
  "brief": "Premium CTV inventory for Q2 campaign"
}
```

### Quick reference

|                        | Buyer-declared (`require_operator_auth: false`) | Account-id namespace (`require_operator_auth: true`) |
| ---------------------- | ----------------------------------------------- | ---------------------------------------------------- |
| **Sandbox accounts**   | Declared by buyer via `sync_accounts`           | Pre-existing on seller's platform                    |
| **Discovery**          | N/A — buyer creates them                        | `list_accounts` with `sandbox: true`                 |
| **Account reference**  | Natural key with `sandbox: true`                | `account_id`                                         |
| **Real-world analogy** | Self-service test mode                          | Stripe test mode, Google Ads sandbox                 |

## Response confirmation

Success responses include `sandbox: true` to confirm the request was processed in sandbox mode:

```json theme={null}
{
  "products": [...],
  "sandbox": true
}
```

## Full lifecycle example (buyer-declared account)

This example shows the buyer-declared account path. For account-id namespaces, resolve the sandbox account through `list_accounts` (or out-of-band onboarding for seller-defined IDs), then replace the natural key account reference with `{ "account_id": "acct_sandbox_acme_001" }` in each step.

### 1. Discover products

```json theme={null}
// get_products
{
  "account": {
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "sandbox": true
  },
  "brief": "CTV inventory for brand awareness"
}
```

### 2. Create a media buy

```json theme={null}
// create_media_buy
{
  "account": {
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "sandbox": true
  },
  "proposal_id": "prop_abc",
  "total_budget": { "amount": 50000, "currency": "USD" },
  "brand": { "domain": "acme-corp.com" },
  "start_time": { "start_type": "asap" },
  "end_time": "2026-04-01T00:00:00Z"
}
```

The seller returns a simulated media buy with realistic IDs, packages, and creative deadlines — nothing is booked on any real platform.

### 3. Upload creatives

```json theme={null}
// sync_creatives
{
  "account": {
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "sandbox": true
  },
  "creatives": [{
    "creative_id": "hero_video_30s",
    "name": "Brand Hero Video 30s",
    "format_id": {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "video_standard_30s"
    },
    "assets": {
      "video": {
        "url": "https://cdn.example.com/hero.mp4",
        "width": 1920,
        "height": 1080,
        "duration_ms": 30000
      }
    }
  }],
  "assignments": {
    "hero_video_30s": ["pkg_001"]
  }
}
```

### 4. Check delivery

```json theme={null}
// get_media_buy_delivery
{
  "account": {
    "brand": { "domain": "acme-corp.com" },
    "operator": "acme-corp.com",
    "sandbox": true
  },
  "media_buy_ids": ["mb_sandbox_123"]
}
```

The seller returns simulated delivery metrics — impressions, spend, pacing — as if the campaign were running.

## Sandbox vs dry run

Some sync tasks (`sync_creatives`, `sync_catalogs`) support a `dry_run` parameter. These serve different purposes:

|                  | Sandbox account          | `dry_run`                                        |
| ---------------- | ------------------------ | ------------------------------------------------ |
| **Meaning**      | Nothing is real          | Preview changes without applying                 |
| **Scope**        | All tasks on the account | Sync tasks only                                  |
| **Side effects** | None (simulated)         | None (preview only)                              |
| **Use case**     | Test the full lifecycle  | Check what a sync would change before committing |

You can combine them — `dry_run: true` on a sandbox account previews the sync without even updating sandbox state.

<Warning>
  The `X-Dry-Run`, `X-Test-Session-ID`, and `X-Mock-Time` HTTP headers are **deprecated**. Sandbox mode replaces them as a protocol-level parameter.

  * **Sellers MUST NOT** alter behavior based on these headers. Sandbox mode is determined solely by the account reference. Sellers SHOULD ignore the headers entirely and MAY log a deprecation warning to help buyers identify stale integrations.
  * **Buyers MUST NOT** rely on these headers to prevent production side effects. Only `sandbox: true` on the account reference guarantees sandbox semantics.
</Warning>

## Seller implementation

When a request references a sandbox account (either via `sandbox: true` in the natural key or via a sandbox `account_id`), agents MUST NOT persist production state or cause real-world side effects:

* **MUST NOT** make real ad platform API calls (no real orders, line items, etc.)
* **MUST NOT** charge real money or create real billing records
* **MUST** validate inputs the same way as production (reject invalid budgets, bad dates, etc.)
* **MUST** return realistic response shapes with simulated data
* **SHOULD** include `sandbox: true` in success responses

Sandbox errors are real validation errors. If a buyer sends an invalid budget using a sandbox account, return a real error — don't simulate fake errors.

For **account-id namespace sellers**: ensure your platform has pre-existing sandbox/test accounts that `list_accounts` can return when filtered with `sandbox: true`, or supply sandbox IDs out-of-band.

For **buyer-declared account sellers**: accept `sandbox: true` as part of the natural key in `sync_accounts` and account references. Treat `(brand, operator, sandbox: true)` as a distinct account from `(brand, operator)`.

## Protocol compliance

Sellers that declare `account.sandbox: true` in capabilities MUST:

* Accept sandbox accounts appropriate to their account model
* Apply sandbox semantics to all requests referencing a sandbox account
* NOT persist production state or cause real-world side effects when processing sandbox requests
* Apply normal input validation (sandbox does not bypass validation)

Sellers SHOULD include `sandbox: true` in success responses when processing a sandbox account request.
