Skip to main content
Make your first AdCP call in 5 minutes against the public test agent.

Setup

Get your API key from the AAO dashboard under API Keys, then:
export ADCP_AUTH_TOKEN="sk_your_api_key_here"
export AGENT_URL="https://test-agent.adcontextprotocol.org/mcp"

1. Discover products

AdCP over MCP uses JSON-RPC 2.0. The transport is Streamable HTTP — responses arrive as server-sent events.
curl -X POST $AGENT_URL \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $ADCP_AUTH_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "get_products",
      "arguments": {
        "brief": "Video ads for pet food brand",
        "brand": { "domain": "premiumpetfoods.com" }
      }
    }
  }'
Response (SSE envelope omitted for clarity):
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"products\":[{\"product_id\":\"pinnacle_news_video_premium\",\"name\":\"Pinnacle News Group video guaranteed\",\"channels\":[\"olv\",\"ctv\"],\"pricing_options\":[{\"pricing_option_id\":\"pinnacle_news_video_premium_pricing_0\",\"pricing_model\":\"cpm\",\"currency\":\"USD\",\"fixed_price\":15}],\"delivery_type\":\"guaranteed\"}, ...],\"sandbox\":true}"
      }
    ]
  }
}
Extract the result — the AdCP payload is JSON-encoded inside content[0].text:
const response = /* parsed JSON-RPC response */;
const payload = JSON.parse(response.result.content[0].text);

console.log(payload.products[0].product_id);    // "pinnacle_news_video_premium"
console.log(payload.products[0].channels);      // ["olv", "ctv"]
console.log(payload.products[0].pricing_options[0].pricing_option_id); // "pinnacle_news_video_premium_pricing_0"
console.log(payload.products[0].pricing_options[0].fixed_price);       // 15

2. Handle errors

Send an invalid tool name to see what errors look like:
curl -X POST $AGENT_URL \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $ADCP_AUTH_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "nonexistent_tool",
      "arguments": {}
    }
  }'
Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"code\":\"INVALID_REQUEST\",\"message\":\"Unknown tool: nonexistent_tool\"}"
      }
    ],
    "isError": true
  }
}
Handle it — check isError, then parse the error payload:
const response = /* parsed JSON-RPC response */;

if (response.result.isError) {
  const err = JSON.parse(response.result.content[0].text);
  console.log(err.code);     // "INVALID_REQUEST"
  console.log(err.message);  // "Unknown tool: nonexistent_tool"
}
Common error codes: INVALID_REQUEST (bad input), RATE_LIMITED (retry with backoff), UNAUTHORIZED (check credentials).

3. Create a media buy

Use the product IDs from step 1 to create a campaign:
curl -X POST $AGENT_URL \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $ADCP_AUTH_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "create_media_buy",
      "arguments": {
        "account": { "account_id": "test_account" },
        "brand": { "domain": "premiumpetfoods.com" },
        "start_time": "asap",
        "end_time": "2026-04-30T00:00:00Z",
        "packages": [{
          "product_id": "pinnacle_news_video_premium",
          "budget": 5000,
          "pricing_option_id": "pinnacle_news_video_premium_pricing_0"
        }]
      }
    }
  }'
Response (IDs will differ on each call):
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"media_buy_id\":\"mb_f4139524\",\"status\":\"active\",\"revision\":1,\"packages\":[{\"package_id\":\"pkg_3df649f0\",\"product_id\":\"pinnacle_news_video_premium\",\"budget\":5000,\"pricing_option_id\":\"pinnacle_news_video_premium_pricing_0\"}],\"valid_actions\":[\"pause\",\"cancel\",\"update_budget\",\"update_dates\",\"update_packages\",\"add_packages\",\"sync_creatives\"],\"sandbox\":true}"
      }
    ]
  }
}
Extract the result:
const response = /* parsed JSON-RPC response */;
const buy = JSON.parse(response.result.content[0].text);

console.log(buy.media_buy_id);     // "mb_f4139524"
console.log(buy.status);           // "active"
console.log(buy.packages[0].budget); // 5000
console.log(buy.valid_actions);    // ["pause", "cancel", "update_budget", ...]

4. Push notifications (webhooks)

Production agents support push notifications for long-running operations. The sandbox test agent does not send webhooks, but you can include the configuration to see how it works. Add push_notification_config to your tool arguments:
{
  "name": "create_media_buy",
  "arguments": {
    "account": { "account_id": "your_account" },
    "brand": { "domain": "premiumpetfoods.com" },
    "start_time": "asap",
    "end_time": "2026-04-30T00:00:00Z",
    "packages": [{ "..." : "..." }],
    "push_notification_config": {
      "url": "https://you.example.com/webhooks/adcp",
      "authentication": {
        "schemes": ["HMAC-SHA256"],
        "credentials": "your_shared_secret_min_32_chars_long"
      }
    }
  }
}
When the operation completes, the agent POSTs to your URL:
{
  "task_id": "task_456",
  "task_type": "create_media_buy",
  "status": "completed",
  "timestamp": "2025-01-22T10:30:00Z",
  "result": {
    "media_buy_id": "mb_12345",
    "packages": [{ "package_id": "pkg_001" }]
  }
}
Verify the HMAC signature before processing:
import { createHmac, timingSafeEqual } from 'crypto';

function handleWebhook(req) {
  const signature = req.headers['x-adcp-signature'];
  const expected = createHmac('sha256', 'your_shared_secret_min_32_chars_long')
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error('Invalid signature');
  }

  const { task_type, status, result } = req.body;
  console.log(result.media_buy_id); // "mb_12345"
}

Using the client library

The examples above use raw HTTP for clarity. In practice, use the AdCP client library which handles SSE parsing, retries, and authentication:
npm install @adcp/client  # JavaScript/TypeScript
pip install adcp          # Python
import { AdCPClient } from '@adcp/client';

const client = new AdCPClient([{
  id: 'test',
  name: 'Test Agent',
  agent_uri: 'https://test-agent.adcontextprotocol.org/mcp',
  protocol: 'mcp',
  auth_token: process.env.ADCP_AUTH_TOKEN,
}]);

const result = await client.agent('test').executeTask('get_products', {
  brief: 'Video ads for pet food brand',
  brand: { domain: 'premiumpetfoods.com' },
});

console.log(result.data.products);

What’s next