# Ad Context Protocol (AdCP) > Generated at: 2026-05-11 > Library: @adcp/sdk v6.19.0 > AdCP major version: 3 > Canonical URL: https://adcontextprotocol.github.io/adcp-client/llms.txt > Note: the `Library` stamp reflects the package.json version at doc-generation time. The narrative below describes the surface that lands on the next-published minor — including any 6.7 helpers documented here ahead of the release tag. ## What is AdCP AdCP is an open protocol for AI agents to buy, manage, and optimize advertising programmatically. It defines MCP tools that agents call on publisher ad servers — discover inventory, create media buys, sync creatives, manage brand safety, and track delivery. Every tool follows request/response JSON schemas; the TypeScript client wraps them with async task handling, conversation context, and governance middleware. ## Are you building a client or a server? - **Client** (calling existing agents): Continue reading — the Quick Start below is for you. - **Server** (implementing an agent that others call): Read `docs/guides/BUILD-AN-AGENT.md` and `docs/migration-5.x-to-6.x.md`. v6 recommended path: ```typescript import { serve } from '@adcp/sdk'; import { createAdcpServerFromPlatform, definePlatform, defineSignalsPlatform, } from '@adcp/sdk/server'; const platform = definePlatform({ capabilities: { specialisms: ['signal-marketplace'] as const, pricingModels: ['cpm'] as const, }, accounts: { resolve: async () => ({ id: 'acc_1', ctx_metadata: {} }), }, signals: defineSignalsPlatform({ getSignals: async (req, ctx) => ({ signals: [/* ... */], sandbox: true }), activateSignal: async (req, ctx) => ({ /* ... */ }), }), }); serve(() => createAdcpServerFromPlatform(platform, { name: 'My Signals Agent', version: '1.0.0', })); // http://localhost:3001/mcp ``` Compile-time enforcement: `RequiredPlatformsFor` catches missing specialism methods. Capability projection auto-derives `get_adcp_capabilities` blocks (`audience_targeting`, `conversion_tracking`, `compliance_testing.scenarios`, etc.). Idempotency, RFC 9421 signing, async tasks, status normalization, and sync-completion webhook auto-emit are framework-owned. Lower-level option: `createAdcpServer({ signals: { getSignals: ... } })` from `@adcp/sdk/server/legacy/v5` — handler-bag API. Still fully supported, the substrate the platform path calls into. Use when you need fine control over individual handlers, mid-migration from a v5 codebase, or custom-shaped tools the platform interface doesn't yet model. `wrapEnvelope(inner, { replayed, context, operationId })` from `@adcp/sdk/server` attaches protocol envelope fields with the per-error-code allowlist (IDEMPOTENCY_CONFLICT drops `replayed`). **Identity helpers (drop `req: unknown` casts on inline platforms).** `definePlatform` / `defineSalesCorePlatform` / `defineSalesIngestionPlatform` / `defineSignalsPlatform` / `defineCreativeBuilderPlatform` / `defineCreativeAdServerPlatform` / `defineCampaignGovernancePlatform` / `defineContentStandardsPlatform` / `definePropertyListsPlatform` / `defineCollectionListsPlatform` / `defineBrandRightsPlatform` / `definePlatformWithCompliance` are pure identity helpers from `@adcp/sdk/server`. They force a concrete platform interface as the parameter type so TypeScript flows `req` / `ctx` typing into nested handler bodies. Class-pattern adopters with explicit property annotations (`sales: SalesCorePlatform & SalesIngestionPlatform = { ... }`) don't need them. **Typed errors instead of `new AdcpError(code, ...)`.** `AuthRequiredError`, `PermissionDeniedError(action)`, `RateLimitedError(retryAfterSeconds)`, `ServiceUnavailableError`, `UnsupportedFeatureError(feature)`, `GovernanceDeniedError`, `PolicyViolationError`, `IdempotencyConflictError`, `InvalidRequestError`, `InvalidStateError`, plus the not-found family (`AccountNotFoundError`, `MediaBuyNotFoundError`, `PackageNotFoundError`, `ProductNotFoundError`, `CreativeNotFoundError`) and the budget / state family. Each maps to its wire error code with `recovery` baked in. Throw from any platform method or `accounts.resolve`. **`composeMethod` cookbook.** To layer `before`/`after` hooks on a single platform method — short-circuit for caching, enrichment under `ext.*`, typed-error guards — use `composeMethod(inner, { before?, after? })` from `@adcp/sdk/server`. Stacking multiple guards: nest `composeMethod` calls (outer `before` runs first). Test patterns (mocking inner, asserting short-circuit, chained hooks, typed-error propagation): see [`docs/recipes/composeMethod-testing.md`](./recipes/composeMethod-testing.md). Pre-built `accounts.resolve` guards from the same package: `requireAccountMatch(predicate, opts)`, `requireAdvertiserMatch(getRoster, opts)`, `requireOrgScope(getAccountOrg, getCtxOrg, opts)`. Default deny returns `null` (indistinguishable from "not found"; guards against principal enumeration); opt in to `onDeny: 'throw'` for typed `PermissionDeniedError`. **Four reference `AccountStore` shapes.** Pick the one whose onboarding model matches yours. **Shape A — `InMemoryImplicitAccountStore`**: `resolution: 'implicit'`, buyer-driven `sync_accounts` populates the auth-principal → accounts map. **Shape B — `createOAuthPassthroughResolver`**: `resolution: 'explicit'`, returns just the `resolve` function for adapters fronting an upstream OAuth listing endpoint (Snap, Meta, TikTok, LinkedIn — `extract bearer → GET /me/adaccounts → match by id`). **Shape C — `createRosterAccountStore`**: `resolution: 'explicit'`, returns a complete `AccountStore` for adopters who own the roster (storefront table, admin-UI-managed JSON). Supports `resolveWithoutRef` for tools that send no `account` field on the wire (`list_creative_formats`, `preview_creative`, `provide_performance_feedback`) — set it to return a synthetic publisher-wide entry instead of `null`. **Shape D — `createDerivedAccountStore`**: `resolution: 'derived'`, single-tenant agents where there is no `account_id` on the wire and the auth principal alone identifies the tenant (audiostack, flashtalking, single-namespace retail-media). Provide `toAccount(ctx)`; the factory throws `AUTH_REQUIRED` on missing-credential calls and ignores buyer-supplied `account_id` (single-tenant by definition). All four live at `@adcp/sdk/server`. **Multi-tenant.** Two helpers, pick by deployment shape. **Host-routed**: `createTenantRegistry({...})` — one server per tenant, tenant-id keyed lookup with `registry.get(tenantId)`. **Account-routed**: `createTenantStore({...})` — one server, per-entry tenant-isolation gate built in (cross-tenant entries on `upsert` / `syncGovernance` rejected with `PERMISSION_DENIED` BEFORE adopter callbacks run; fail-closed when the auth principal can't be resolved). `createTenantStore` mitigates the canonical multi-tenant write-across-tenants bug class at the SDK layer rather than relying on adopter discipline. **`BuyerAgentRegistry`** — durable buyer-agent identity surface. `BuyerAgentRegistry.signingOnly({ resolveByAgentUrl })` (production target — only `http_sig` credentials route through), `bearerOnly({ resolveByCredential })` (pre-trust beta — bearer/api-key/oauth all route), `mixed(...)` (transition posture). Wrap with `BuyerAgentRegistry.cached(inner, { ttlSeconds })` for TTL + LRU + concurrent-resolve coalescing. The resolved `BuyerAgent` flows through `ctx.agent` to every `AccountStore` method (`resolve` / `upsert` / `list` / `syncGovernance` / `reportUsage` / `getAccountFinancials`) and to `tasks_get` polling. `BuyerAgent.status === 'suspended' | 'blocked'` triggers framework-level `PERMISSION_DENIED`. `BuyerAgent.sandbox_only: true` rejects requests against non-sandbox accounts. See [`docs/migration-buyer-agent-registry.md`](./migration-buyer-agent-registry.md) for the full surface. **Lifecycle helpers.** `MEDIA_BUY_TRANSITIONS` and `CREATIVE_ASSET_TRANSITIONS` (the canonical state-graph maps the storyboard runner uses), plus `isLegalMediaBuyTransition(from, to)` / `assertMediaBuyTransition(from, to)` and the creative pair. `assertMediaBuyTransition` throws `AdcpError` with the spec-correct code (`NOT_CANCELLABLE` for the cancel-idempotency path, `INVALID_STATE` everywhere else). Production sellers that enforce transitions with these helpers cannot drift from conformance enforcement. `createMediaBuyStore({ store })` opt-in framework wiring handles the `packages[].targeting_overlay` echo contract on `get_media_buys` (sellers claiming `property-lists` / `collection-lists` MUST echo the persisted list reference). **Breaking in 6.7 — audit before bumping.** (1) `accounts.resolution: 'implicit'` now actually refuses inline `{account_id}` references with `INVALID_REQUEST` (pre-6.7 the docstring claimed this but nothing checked it). Adopters whose callers passed inline `account_id` against an `'implicit'` platform must drop to `'explicit'` or fix callers to use `sync_accounts` first. (2) `SalesPlatform` is now structurally `SalesCorePlatform & SalesIngestionPlatform` with all methods individually optional. Adopters with `: SalesPlatform` field annotations claiming `sales-non-guaranteed` / `-guaranteed` / `-broadcast-tv` / `-catalog-driven` need to switch the annotation to `: SalesCorePlatform & SalesIngestionPlatform` (or use `defineSalesCorePlatform` + `defineSalesIngestionPlatform` spread). Self-announcing under `tsc --noEmit`. Walled-garden CAPI specialisms (`sales-social`) drop ~40 LOC of stub-throw boilerplate. Full migration recipe at [`docs/migration-6.6-to-6.7.md`](./migration-6.6-to-6.7.md). **`Account` v3 wire fields.** `Account` gained `billing_entity`, `rate_card`, `payment_terms`, `credit_limit`, `setup` (drives `pending_approval` → `active` lifecycle), `account_scope`, `governance_agents`, and `reporting_bucket` — all optional. `billing_entity.bank` and `governance_agents[i].authentication.credentials` are stripped on emit per spec; `Account.authInfo` is now optional. `AccountStore.upsert` / `list` / `syncGovernance` accept an optional `ResolveContext` second argument carrying `authInfo` / `toolName` / `agent` for principal-keyed gating. **`refAccountId(ref)`** narrows `AccountReference` to its `account_id` arm without casting (returns `undefined` for missing refs, `{brand, operator}` arms, sandbox arms). `narrowAccountRef(ref)` returns the typed arm or `null` for full discriminated-union narrowing. **`NoAccountCtx`** is the request-context type for tools whose wire request doesn't carry an `account` field (`previewCreative`, `listCreativeFormats`, `providePerformanceFeedback`); `ctx.account` is `Account | undefined` and adopters either return a singleton from `accounts.resolve(undefined)` or guard with `if (ctx.account == null) ...`. **Validation hints on every `VALIDATION_ERROR` envelope.** `ValidationIssue` carries `hint` (one-sentence curated recipe for known shape gotchas — `activation_key` discriminator nesting, `account` discriminator merging, `budget` shape, `format_id` object, VAST/DAAST `delivery_type`, missing `idempotency_key`, log_event/CAPI projection), `discriminator` (which `oneOf` branch the validator inferred), and `schemaId` (the `$id` of the rejecting schema). Buyer-side recovery order: `hint` first, then `discriminator`, then `variants`, then `pointer` + `keyword`. `oneOf` near-miss diagnostics now point at the Success-arm residuals when a Success-vs-Error envelope payload populates Success-only fields. **Other adopter-facing surfaces.** `DecisioningPlatform.instructions` accepts a function form (`(ctx: SessionContext) => string | undefined`) for per-session prose under `serve({ reuseAgent: false })`. `listCreativeFormats?` is now typed on `CreativeBuilderPlatform` and `CreativeAdServerPlatform` (drops the v5 `opts.creative.listCreativeFormats` escape hatch). `update_rights` is a first-class brand-rights tool with `creative_approval` webhook builders. `@adcp/sdk/upstream-recorder` is a sandbox-only producer-side middleware for the `query_upstream_traffic` storyboard check; `@adcp/sdk/mock-server` is a public sub-export for in-process integration tests. `runStoryboard({ agents })` routes per-specialism storyboard steps to multiple agents (matching `/sales`, `/signals`, `/governance`, `/creative`, `/brand` topology). `media_buy_ids[]` fan-out on `getMediaBuyDelivery` / `getCreativeDelivery` is platform-side pass-through (framework hands the array as-is); a dev-mode warning fires when handlers return fewer rows than requested. **Don't put credentials in `ctx_metadata`.** Wire-strip protects buyer responses but not server-side log lines, error envelopes, heap dumps, or adopter-generated strings. Re-derive bearers per request from `ctx.authInfo` + your token cache; embed only non-secret upstream IDs in `ctx_metadata`. See [`docs/guides/CTX-METADATA-SAFETY.md`](./guides/CTX-METADATA-SAFETY.md). ## Quick Start (Client) ```typescript import { ADCPMultiAgentClient } from '@adcp/sdk'; const client = ADCPMultiAgentClient.simple('https://agent.example.com/mcp/', { authToken: process.env.ADCP_TOKEN, }); const agent = client.agent('default-agent'); // Discover products const products = await agent.getProducts({ buying_mode: 'brief', brief: 'coffee brands' }); if (products.status === 'completed') console.log(products.data.products); // Create a media buy const buy = await agent.createMediaBuy({ account: { account_id: 'acct_1' }, brand: { domain: 'coffee.example.com' }, start_time: 'asap', end_time: '2026-06-01T00:00:00Z', packages: [{ buyer_ref: 'pkg-1', product_id: 'prod_1', pricing_option_id: 'cpm_1', budget: 5000 }], }); ``` ## Error Handling When `result.success` is `false`, use `result.adcpError` for programmatic handling: - `result.error` — Human-readable string (e.g., `"RATE_LIMITED: Too many requests"`) - `result.adcpError.code` — Error code (e.g., `RATE_LIMITED`, `INVALID_REQUEST`) - `result.adcpError.recovery` — `'transient'` (retry), `'correctable'` (fix request), or `'terminal'` (give up) - `result.adcpError.retryAfterMs` — Milliseconds to wait before retrying - `result.adcpError.field` / `result.adcpError.suggestion` — Hints for correctable errors - `result.adcpError.synthetic` — `true` when inferred from unstructured text - `result.correlationId` — Correlation ID for tracing across agents Use `isRetryable(result)` and `getRetryDelay(result)` for retry logic. `TaskResult` is a discriminated union — `if (result.success)` narrows `data` to `T`; `if (!result.success)` guarantees `error: string` and `status: 'failed'`. ```typescript if (!result.success) { if (isRetryable(result)) { await sleep(getRetryDelay(result)); // ms, defaults to 5000 } else if (result.adcpError?.recovery === 'correctable') { console.log('Fix:', result.adcpError.suggestion, 'Field:', result.adcpError.field); } else { console.error(result.error, 'Correlation:', result.correlationId); } } ``` For exhaustive handling across all seven statuses, prefer the `match()` dispatcher (fluent method on every result returned from the SDK, or free function import): ```typescript const label = result.match!({ completed: r => `OK: ${JSON.stringify(r.data)}`, failed: r => `Error: ${r.adcpError?.code ?? r.error}`, submitted: r => `Pending: poll ${r.metadata.taskId}`, 'governance-denied': r => `Denied: ${r.adcpError?.code ?? r.error}`, working: r => `Running: ${r.metadata.taskId}`, 'input-required': r => `Needs input: ${r.metadata.inputRequest?.question}`, deferred: r => `Deferred: ${r.deferred?.token}`, }); // Optional `_` catchall makes every arm optional: // const label = result.match!({ completed: r => JSON.stringify(r.data), _: r => r.status }); ``` TypeScript enforces exhaustiveness at compile time when the `_` catchall is omitted — missing an arm is a type error, not a runtime surprise. The `!` is because `TaskResultBase.match` is declared optional so hand-constructed result literals (tests, middleware) stay valid; every result returned from the SDK has `.match` attached. For hand-constructed literals, use the free function `match(result, handlers)` or call `attachMatch(result)` first. ## Idempotency (mutating requests) AdCP v3 requires `idempotency_key` on every mutating request (`create_media_buy`, `update_media_buy`, `activate_signal`, all `sync_*`, `si_send_message`, etc.). The SDK auto-generates a UUID v4 when callers don't supply one, reuses it across internal retries, and surfaces it on the result: ```typescript const result = await client.createMediaBuy({ account, brand, start_time, end_time, packages }); result.metadata.idempotency_key // key that was sent (auto-generated or caller-supplied) result.metadata.replayed // true if this was a cached replay from a prior retry ``` **Two things agents with side effects MUST handle:** 1. **Side-effect suppression on `replayed: true`.** If your agent emits notifications, writes LLM memory, or fires downstream tool calls on the response, check `result.metadata.replayed` before acting. A cached replay means the side effects already fired on the original call. ```typescript if (result.success && !result.metadata.replayed) { await notify(`Campaign ${result.data.media_buy_id} created`); await memory.write({ campaign_id: result.data.media_buy_id }); } ``` 2. **Agent re-plan vs. network retry.** A network retry (same bytes, socket timeout) reuses the same key — the SDK handles this. An agent re-plan (LLM re-ran its planner and produced a different payload) means a NEW intent — mint a fresh key by calling the method again without passing one. Reusing the prior key with a different payload returns `IdempotencyConflictError`. **Typed errors:** on failure, `result.errorInstance` carries a typed `ADCPError` subclass for codes with dedicated classes — currently `IdempotencyConflictError` and `IdempotencyExpiredError`. Prefer `instanceof` checks over switching on `adcpError.code` strings. ```typescript import { IdempotencyConflictError, IdempotencyExpiredError } from '@adcp/sdk'; if (result.errorInstance instanceof IdempotencyConflictError) { // Agent re-planned with different payload. Retry with a fresh key. // result.errorInstance.idempotencyKey carries the key the server omitted. } if (result.errorInstance instanceof IdempotencyExpiredError) { // Key past replay window. If you know the prior call succeeded, look up // by natural key (e.g., get_media_buys by context.internal_campaign_id). // Otherwise mint a fresh key. } ``` **BYOK** (persist keys in your DB across process restarts): you own the replay-window boundary. Ask the client for the seller's declared TTL: ```typescript const ttl = await client.getIdempotencyReplayTtlSeconds(); // Returns the declared number. Throws ConfigurationError if the seller is v3 // but omits adcp.idempotency.replay_ttl_seconds — the SDK does NOT default to // 24h, because a silent default misleads retry-sensitive flows. Returns // undefined on v2 sellers (pre-idempotency-envelope). ``` Pass your persisted key with `useIdempotencyKey(key)` — it validates against the spec pattern (`^[A-Za-z0-9_.:-]{16,255}$`) before the network round-trip: ```typescript import { useIdempotencyKey } from '@adcp/sdk'; const key = await db.getOrCreateIdempotencyKey(campaign.id); await client.createMediaBuy({ ...params, ...useIdempotencyKey(key) }); ``` **Crash-recovery cookbook.** For an end-to-end recipe (natural-key lookup after restart, `IdempotencyConflictError` / `IdempotencyExpiredError` handling, `metadata.replayed` as side-effect gate, Postgres schema), see [`docs/guides/idempotency-crash-recovery.md`](./guides/idempotency-crash-recovery.md). ## ext.adcp Extension Namespace **`ext.adcp.*` namespace.** The SDK reserves keys under `ext.adcp.*` for read-by-agent extensions that don't yet warrant their own AdCP spec field. Agents that recognize a key act on it; agents that don't recognize it ignore it silently (per AdCP `ext` semantics: accepted-without-error). The namespace is transport-neutral — it travels in the `ext` envelope field on both MCP and A2A transports. Keys in this namespace are hints **inbound to seller/responder agents** from the SDK or test tooling; **buyer agents building production flows MUST NOT emit `ext.adcp.*` keys**. | Key | Stamped by | Purpose | |-----|-----------|---------| | `ext.adcp.disable_sandbox` | `adcp storyboard run --no-sandbox` | Hint (value: `true`) to bypass internal sandbox routing and exercise real adapter paths. Seller agents that honor this key serve production-shaped responses regardless of internal sandbox heuristics (env-var fallbacks, brand-domain detection, fixture substitutes). | Third-party extensions MUST use a distinct namespace (e.g. `ext.com.example.*`) to avoid collisions with future `ext.adcp.*` keys. ## Tools Every tool is an MCP tool called via `agent.(params)`. Returns `TaskResult` with `status`, `data`, `error`, `adcpError`, `correlationId`, `deferred`, or `submitted`. ### Protocol #### `get_adcp_capabilities` Request parameters for cross-protocol capability discovery. **Request:** - Optional: `protocols: string[]`, `context: Context` **Response (success branch):** - Required: `adcp: object`, `supported_protocols: string[]` - Optional: `account: object`, `media_buy: object`, `signals: object`, `governance: object`, `sponsored_intelligence: object`, `brand: object`, `creative: object`, `request_signing: object`, +9 more ### Account Management #### `list_accounts` Request parameters for listing accounts accessible to the authenticated agent. **Request:** - Optional: `status: 'active' | 'pending_approval' | 'rejected' | 'payment_required' | 'suspended' | 'closed'`, `pagination: Pagination Request`, `sandbox: boolean`, `context: Context` **Response (success branch):** - Required: `accounts: object[]` - Optional: `errors: object[]`, `pagination: Pagination Response`, `context: Context` #### `sync_accounts` Request parameters for syncing advertiser accounts with a seller. **Request:** - Required: `idempotency_key: string`, `accounts: object[]` - Optional: `delete_missing: boolean`, `dry_run: boolean`, `push_notification_config: Push Notification Config`, `context: Context` **Response (success branch):** - Required: `accounts: object[]` - Optional: `dry_run: boolean`, `context: Context` #### `sync_governance` Request parameters for registering governance agent endpoints on accounts. **Request:** - Required: `idempotency_key: string`, `accounts: object[]` - Optional: `context: Context` **Response (success branch):** - Required: `accounts: object[]` - Optional: `context: Context` #### `report_usage` Request parameters for reporting vendor service consumption after delivery. **Request:** - Required: `idempotency_key: string`, `reporting_period: Datetime Range`, `usage: object[]` - Optional: `context: Context` **Response (success branch):** - Required: `accepted: integer` - Optional: `errors: object[]`, `sandbox: boolean`, `context: Context` #### `get_account_financials` Request parameters for querying financial status of an operator-billed account. **Request:** - Required: `account: Account Ref` - Optional: `period: Date Range`, `context: Context` **Response (success branch):** - Required: `account: Account Ref`, `currency: string`, `period: Date Range`, `timezone: string` - Optional: `spend: object`, `credit: object`, `balance: object`, `payment_status: 'current' | 'past_due' | 'suspended'`, `payment_terms: Payment Terms`, `invoices: object[]`, `context: Context` **Deep dive:** - docs/getting-started.md — authentication and account setup ### Media Buying #### `get_products` Request parameters for discovering available advertising products. **Request:** - Required: `buying_mode: 'brief' | 'wholesale' | 'refine'` - Optional: `brief: string`, `refine: object[]`, `brand: Brand Ref`, `catalog: Catalog`, `account: Account Ref`, `preferred_delivery_types: object[]`, `filters: Product Filters`, `property_list: Property List Ref`, +5 more **Response (success branch):** - Required: `products: object[]` - Optional: `proposals: object[]`, `errors: object[]`, `property_list_applied: boolean`, `catalog_applied: boolean`, `refinement_applied: object[]`, `incomplete: object[]`, `pagination: Pagination Response`, `sandbox: boolean`, +1 more #### `list_creative_formats` Request parameters for discovering format IDs and creative agents supported by this sales agent. **Request:** - Optional: `format_ids: object[]`, `asset_types: object[]`, `max_width: integer`, `max_height: integer`, `min_width: integer`, `min_height: integer`, `is_responsive: boolean`, `name_search: string`, +7 more **Response (success branch):** - Required: `formats: object[]` - Optional: `creative_agents: object[]`, `errors: object[]`, `pagination: Pagination Response`, `sandbox: boolean`, `context: Context` **Watch out:** - Each `renders[]` entry satisfies a `oneOf` — exactly one of `dimensions` (object) OR `parameters_from_format_id: true`. A render with only `{ role }` (or `{ role, duration_seconds }`) fails validation. - Use the typed factories from `@adcp/sdk`: `displayRender({ role, dimensions })` for display/video; `parameterizedRender({ role })` for audio and template formats (auto-injects `parameters_from_format_id: true`). - Audio formats (`type: "audio"`) have no width/height — declare `renders: [parameterizedRender({ role: "primary" })]` and encode duration/codec in `format_id.parameters` (declared via `accepts_parameters`). #### `create_media_buy` Request parameters for creating a media buy. **Request:** - Required: `idempotency_key: string`, `account: Account Ref`, `brand: Brand Ref`, `start_time: Start Timing`, `end_time: string` - Optional: `plan_id: string`, `proposal_id: string`, `total_budget: object`, `packages: object[]`, `advertiser_industry: Advertiser Industry`, `invoice_recipient: Business Entity`, `io_acceptance: object`, `po_number: string`, +5 more **Response (success branch):** - Required: `media_buy_id: string`, `packages: object[]` - Optional: `account: Account`, `invoice_recipient: Business Entity`, `status: Media Buy Status`, `confirmed_at: string`, `creative_deadline: string`, `revision: integer`, `valid_actions: object[]`, `planned_delivery: Planned Delivery`, +2 more #### `update_media_buy` Request parameters for updating campaign and package settings. **Request:** - Required: `account: Account Ref`, `media_buy_id: string`, `idempotency_key: string` - Optional: `revision: integer`, `paused: boolean`, `canceled: 'true'`, `cancellation_reason: string`, `start_time: Start Timing`, `end_time: string`, `packages: object[]`, `invoice_recipient: Business Entity`, +4 more **Response (success branch):** - Required: `media_buy_id: string` - Optional: `status: Media Buy Status`, `revision: integer`, `implementation_date: string,null`, `invoice_recipient: Business Entity`, `affected_packages: object[]`, `valid_actions: object[]`, `sandbox: boolean`, `context: Context` #### `get_media_buys` Request parameters for retrieving media buy status, creative approvals, and delivery snapshots. **Request:** - Optional: `account: Account Ref`, `media_buy_ids: string[]`, `status_filter: Media Buy Status | object[]`, `include_snapshot: boolean`, `include_history: integer`, `pagination: Pagination Request`, `context: Context` **Response (success branch):** - Required: `media_buys: object[]` - Optional: `errors: object[]`, `pagination: Pagination Response`, `sandbox: boolean`, `context: Context` #### `get_media_buy_delivery` Request parameters for retrieving comprehensive delivery metrics. **Request:** - Optional: `account: Account Ref`, `media_buy_ids: string[]`, `status_filter: Media Buy Status | object[]`, `start_date: string`, `end_date: string`, `include_package_daily_breakdown: boolean`, `attribution_window: object`, `reporting_dimensions: object`, +1 more **Response (success branch):** - Required: `reporting_period: object`, `currency: string`, `media_buy_deliveries: object[]` - Optional: `notification_type: 'scheduled' | 'final' | 'delayed' | 'adjusted' | 'window_update'`, `partial_data: boolean`, `unavailable_count: integer`, `sequence_number: integer`, `next_expected_at: string`, `attribution_window: Attribution Window`, `aggregated_totals: object`, `errors: object[]`, +2 more #### `provide_performance_feedback` Request parameters for sharing performance outcomes with publishers. **Request:** - Required: `media_buy_id: string`, `idempotency_key: string`, `measurement_period: Datetime Range`, `performance_index: number` - Optional: `package_id: string`, `creative_id: string`, `metric_type: Metric Type`, `feedback_source: Feedback Source`, `context: Context` **Response (success branch):** - Required: `success: 'true'` - Optional: `sandbox: boolean`, `context: Context` #### `sync_event_sources` Request parameters for configuring event sources on an account. **Request:** - Required: `idempotency_key: string`, `account: Account Ref` - Optional: `event_sources: object[]`, `delete_missing: boolean`, `context: Context` **Response (success branch):** - Required: `event_sources: object[]` - Optional: `sandbox: boolean`, `context: Context` #### `log_event` Request parameters for logging conversion or marketing events. **Request:** - Required: `event_source_id: string`, `events: object[]`, `idempotency_key: string` - Optional: `test_event_code: string`, `context: Context` **Response (success branch):** - Required: `events_received: integer`, `events_processed: integer` - Optional: `partial_failures: object[]`, `warnings: string[]`, `match_quality: number`, `sandbox: boolean`, `context: Context` #### `sync_audiences` Request parameters for managing CRM-based audiences on an account. **Request:** - Required: `idempotency_key: string`, `account: Account Ref` - Optional: `audiences: object[]`, `delete_missing: boolean`, `context: Context` **Response (success branch):** - Required: `audiences: object[]` - Optional: `sandbox: boolean`, `context: Context` #### `sync_catalogs` Request parameters for syncing catalog feeds (products, inventory, stores, promotions, offerings) with approval workflow. **Request:** - Required: `idempotency_key: string`, `account: Account Ref` - Optional: `catalogs: object[]`, `catalog_ids: string[]`, `delete_missing: boolean`, `dry_run: boolean`, `validation_mode: Validation Mode`, `push_notification_config: Push Notification Config`, `context: Context` **Response (success branch):** - Required: `catalogs: object[]` - Optional: `dry_run: boolean`, `sandbox: boolean`, `context: Context` **Deep dive:** - docs/getting-started.md — installation, auth, basic usage - docs/guides/ASYNC-DEVELOPER-GUIDE.md — async task patterns (submitted, deferred, input-required) - docs/guides/PUSH-NOTIFICATION-CONFIG.md — webhook setup for delivery reports - docs/guides/REAL-WORLD-EXAMPLES.md — end-to-end buying flows ### Creative #### `build_creative` Request parameters for AI-powered creative generation. **Request:** - Required: `idempotency_key: string` - Optional: `message: string`, `creative_manifest: Creative Manifest`, `creative_id: string`, `concept_id: string`, `media_buy_id: string`, `package_id: string`, `target_format_id: Format Id`, `target_format_ids: object[]`, +10 more **Response (success branch):** - Required: `creative_manifest: Creative Manifest` - Optional: `sandbox: boolean`, `expires_at: string`, `preview: object`, `preview_error: Error`, `pricing_option_id: string`, `vendor_cost: number`, `currency: string`, `consumption: Creative Consumption`, +1 more **Watch out:** - Response is ALWAYS `{ creative_manifest }` (single) or `{ creative_manifests }` (multi). Platform-native fields at the top level (`tag_url`, `creative_id`, `media_type`) are invalid. - Use `buildCreativeResponse({ creative_manifest })` / `buildCreativeMultiResponse({ creative_manifests })` from `@adcp/sdk/server` to enforce the shape at compile time. - Each asset under `creative_manifest.assets` needs an `asset_type` discriminator — use the factories: `imageAsset`, `videoAsset`, `audioAsset`, `htmlAsset`, `urlAsset`, `textAsset` (or `Asset.image(...)`). #### `preview_creative` Request parameters for generating creative previews. **Request:** - Required: `request_type: 'single' | 'batch' | 'variant'` - Optional: `creative_manifest: Creative Manifest`, `format_id: Format Id`, `inputs: object[]`, `template_id: string`, `quality: Creative Quality`, `output_format: Preview Output Format`, `item_limit: integer`, `requests: object[]`, +3 more **Response (success branch):** - Required: `response_type: 'single'`, `previews: object[]`, `expires_at: string` - Optional: `interactive_url: string`, `context: Context` **Watch out:** - Each `renders[]` entry is a oneOf on `output_format` — use `urlRender({...})`, `htmlRender({...})`, or `bothRender({...})` to inject the discriminator and require the matching `preview_url`/`preview_html` field. #### `list_creative_formats` Request parameters for discovering creative formats from this creative agent. **Request:** - Optional: `format_ids: object[]`, `type: 'audio' | 'video' | 'display' | 'dooh'`, `asset_types: string[]`, `max_width: integer`, `max_height: integer`, `min_width: integer`, `min_height: integer`, `is_responsive: boolean`, +10 more **Response (success branch):** - Required: `formats: object[]` - Optional: `creative_agents: object[]`, `errors: object[]`, `pagination: Pagination Response`, `context: Context` **Watch out:** - Each `renders[]` entry satisfies a `oneOf` — exactly one of `dimensions` (object) OR `parameters_from_format_id: true`. A render with only `{ role }` (or `{ role, duration_seconds }`) fails validation. - Use the typed factories from `@adcp/sdk`: `displayRender({ role, dimensions })` for display/video; `parameterizedRender({ role })` for audio and template formats (auto-injects `parameters_from_format_id: true`). - Audio formats (`type: "audio"`) have no width/height — declare `renders: [parameterizedRender({ role: "primary" })]` and encode duration/codec in `format_id.parameters` (declared via `accepts_parameters`). #### `get_creative_delivery` Request parameters for retrieving creative delivery data with variant-level breakdowns. **Request:** - Optional: `account: Account Ref`, `media_buy_ids: string[]`, `creative_ids: string[]`, `start_date: string`, `end_date: string`, `max_variants: integer`, `pagination: Pagination Request`, `context: Context` **Response (success branch):** - Required: `currency: string`, `reporting_period: object`, `creatives: object[]` - Optional: `account_id: string`, `media_buy_id: string`, `pagination: object`, `errors: object[]`, `context: Context` #### `list_creatives` Request parameters for querying creative library with filtering and pagination. **Request:** - Optional: `filters: Creative Filters`, `sort: object`, `pagination: Pagination Request`, `include_assignments: boolean`, `include_snapshot: boolean`, `include_items: boolean`, `include_variables: boolean`, `include_pricing: boolean`, +3 more **Response (success branch):** - Required: `query_summary: object`, `pagination: Pagination Response`, `creatives: object[]` - Optional: `format_summary: object`, `status_summary: object`, `errors: object[]`, `sandbox: boolean`, `context: Context` #### `sync_creatives` Request parameters for syncing creative assets with upsert semantics. **Request:** - Required: `account: Account Ref`, `creatives: object[]`, `idempotency_key: string` - Optional: `creative_ids: string[]`, `assignments: object[]`, `delete_missing: boolean`, `dry_run: boolean`, `validation_mode: Validation Mode`, `push_notification_config: Push Notification Config`, `context: Context` **Response (success branch):** - Required: `creatives: object[]` - Optional: `dry_run: boolean`, `sandbox: boolean`, `context: Context` **Deep dive:** - docs/guides/BUILD-AN-AGENT.md — building a creative agent (server-side) - schemas/cache/latest/creative/asset-types/index.json — asset type definitions ### Signals #### `get_signals` Request parameters for discovering signals based on description. **Request:** - Optional: `account: Account Ref`, `signal_spec: string`, `signal_ids: object[]`, `destinations: object[]`, `countries: string[]`, `filters: Signal Filters`, `max_results: integer`, `pagination: Pagination Request`, +1 more **Response (success branch):** - Required: `signals: object[]` - Optional: `errors: object[]`, `pagination: Pagination Response`, `sandbox: boolean`, `context: Context` #### `activate_signal` Request parameters for activating a signal on a specific platform/account. **Request:** - Required: `signal_agent_segment_id: string`, `destinations: object[]`, `idempotency_key: string` - Optional: `action: 'activate' | 'deactivate'`, `pricing_option_id: string`, `account: Account Ref`, `context: Context` **Response (success branch):** - Required: `deployments: object[]` - Optional: `sandbox: boolean`, `context: Context` **Deep dive:** - docs/guides/BUILD-AN-AGENT.md — signals agent example ### Governance #### `create_property_list` Request parameters for creating a new property list. **Request:** - Required: `name: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `description: string`, `base_properties: object[]`, `filters: Property List Filters`, `brand: Brand Ref`, `context: Context` **Response (success branch):** - Required: `list: Property List`, `auth_token: string` - Optional: `replayed: boolean`, `context: Context` #### `update_property_list` Request parameters for updating an existing property list. **Request:** - Required: `list_id: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `name: string`, `description: string`, `base_properties: object[]`, `filters: Property List Filters`, `brand: Brand Ref`, `webhook_url: string`, `context: Context` **Response (success branch):** - Required: `list: Property List` - Optional: `replayed: boolean`, `context: Context` #### `get_property_list` Request parameters for retrieving a property list with resolved properties. **Request:** - Required: `list_id: string` - Optional: `account: Account Ref`, `resolve: boolean`, `pagination: object`, `context: Context` **Response (success branch):** - Required: `list: Property List` - Optional: `identifiers: object[]`, `pagination: Pagination Response`, `resolved_at: string`, `cache_valid_until: string`, `coverage_gaps: object`, `context: Context` #### `list_property_lists` Request parameters for listing property lists. **Request:** - Optional: `account: Account Ref`, `name_contains: string`, `pagination: Pagination Request`, `context: Context` **Response (success branch):** - Required: `lists: object[]` - Optional: `pagination: Pagination Response`, `context: Context` #### `delete_property_list` Request parameters for deleting a property list. **Request:** - Required: `list_id: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `context: Context` **Response (success branch):** - Required: `deleted: boolean`, `list_id: string` - Optional: `replayed: boolean`, `context: Context` #### `create_collection_list` Request parameters for creating a new collection list. **Request:** - Required: `name: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `description: string`, `base_collections: object[]`, `filters: Collection List Filters`, `brand: Brand Ref`, `context: Context` **Response (success branch):** - Required: `list: Collection List`, `auth_token: string` - Optional: `replayed: boolean`, `context: Context` #### `update_collection_list` Request parameters for updating an existing collection list. **Request:** - Required: `list_id: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `name: string`, `description: string`, `base_collections: object[]`, `filters: Collection List Filters`, `brand: Brand Ref`, `webhook_url: string`, `context: Context` **Response (success branch):** - Required: `list: Collection List` - Optional: `replayed: boolean`, `context: Context` #### `get_collection_list` Request parameters for retrieving a collection list with resolved collections. **Request:** - Required: `list_id: string` - Optional: `account: Account Ref`, `resolve: boolean`, `pagination: object`, `context: Context` **Response (success branch):** - Required: `list: Collection List` - Optional: `collections: object[]`, `pagination: Pagination Response`, `resolved_at: string`, `cache_valid_until: string`, `coverage_gaps: object`, `context: Context` #### `list_collection_lists` Request parameters for listing collection lists. **Request:** - Optional: `account: Account Ref`, `name_contains: string`, `pagination: Pagination Request`, `context: Context` **Response (success branch):** - Required: `lists: object[]` - Optional: `pagination: Pagination Response`, `context: Context` #### `delete_collection_list` Request parameters for deleting a collection list. **Request:** - Required: `list_id: string`, `idempotency_key: string` - Optional: `account: Account Ref`, `context: Context` **Response (success branch):** - Required: `deleted: boolean`, `list_id: string` - Optional: `replayed: boolean`, `context: Context` #### `list_content_standards` Request parameters for listing content standards configurations. **Request:** - Optional: `channels: object[]`, `languages: string[]`, `countries: string[]`, `pagination: Pagination Request`, `context: Context` **Response (success branch):** - Required: `standards: object[]` - Optional: `pagination: Pagination Response`, `context: Context` #### `get_content_standards` Request parameters for retrieving a specific standards configuration. **Request:** - Required: `standards_id: string` - Optional: `context: Context` **Response (success branch):** - Optional: `context: Context` #### `create_content_standards` Request parameters for creating a new content standards configuration. **Request:** - Required: `scope: object`, `idempotency_key: string` - Optional: `registry_policy_ids: string[]`, `policies: object[]`, `calibration_exemplars: object`, `context: Context` **Response (success branch):** - Required: `standards_id: string` - Optional: `context: Context` #### `update_content_standards` Request parameters for updating an existing content standards configuration. **Request:** - Required: `standards_id: string`, `idempotency_key: string` - Optional: `scope: object`, `registry_policy_ids: string[]`, `policies: object[]`, `calibration_exemplars: object`, `context: Context` **Response (success branch):** - Required: `success: 'true'`, `standards_id: string` - Optional: `context: Context` #### `calibrate_content` Request parameters for collaborative calibration dialogue. **Request:** - Required: `standards_id: string`, `artifact: Artifact`, `idempotency_key: string` - Optional: `context: Context` **Response (success branch):** - Required: `verdict: Binary Verdict` - Optional: `confidence: number`, `explanation: string`, `features: object[]`, `context: Context` #### `validate_content_delivery` Request parameters for batch validating delivery records. **Request:** - Required: `standards_id: string`, `records: object[]` - Optional: `feature_ids: string[]`, `include_passed: boolean`, `context: Context` **Response (success branch):** - Required: `summary: object`, `results: object[]` - Optional: `context: Context` #### `get_media_buy_artifacts` Request parameters for retrieving content artifacts from a media buy. **Request:** - Required: `media_buy_id: string` - Optional: `account: Account Ref`, `package_ids: string[]`, `failures_only: boolean`, `time_range: object`, `pagination: object`, `context: Context` **Response (success branch):** - Required: `media_buy_id: string`, `artifacts: object[]` - Optional: `collection_info: object`, `pagination: Pagination Response`, `context: Context` #### `get_creative_features` Request parameters for evaluating creative features from a governance agent. **Request:** - Required: `creative_manifest: Creative Manifest` - Optional: `feature_ids: string[]`, `account: Account Ref`, `context: Context` **Response (success branch):** - Required: `results: object[]` - Optional: `detail_url: string`, `pricing_option_id: string`, `vendor_cost: number`, `currency: string`, `consumption: Creative Consumption`, `context: Context` #### `sync_plans` Push campaign plans to the governance agent. **Request:** - Required: `idempotency_key: string`, `plans: object[]` - Optional: `context: Context` **Response (success branch):** - Required: `plans: object[]` - Optional: `replayed: boolean`, `context: Context` #### `report_plan_outcome` Report the outcome of an action to the governance agent. **Request:** - Required: `plan_id: string`, `idempotency_key: string`, `outcome: Outcome Type`, `governance_context: string` - Optional: `check_id: string`, `purchase_type: Purchase Type`, `seller_response: object`, `delivery: object`, `error: object`, `context: Context` **Response (success branch):** - Required: `outcome_id: string`, `status: 'accepted' | 'findings'` - Optional: `committed_budget: number`, `findings: object[]`, `plan_summary: object`, `replayed: boolean`, `context: Context` #### `get_plan_audit_logs` Retrieve governance state and audit trail for a plan. **Request:** - Optional: `plan_ids: string[]`, `portfolio_plan_ids: string[]`, `governance_contexts: string[]`, `purchase_types: object[]`, `include_entries: boolean`, `context: Context` **Response (success branch):** - Required: `plans: object[]` - Optional: `context: Context` #### `check_governance` Orchestrator or seller calls the governance agent to validate an action against the campaign plan. **Request:** - Required: `plan_id: string`, `caller: string` - Optional: `purchase_type: Purchase Type`, `tool: string`, `payload: object`, `governance_context: string`, `phase: Governance Phase`, `planned_delivery: Planned Delivery`, `delivery_metrics: object`, `modification_summary: string`, +2 more **Response (success branch):** - Required: `check_id: string`, `status: Governance Decision`, `plan_id: string`, `explanation: string` - Optional: `findings: object[]`, `conditions: object[]`, `expires_at: string`, `next_check: string`, `categories_evaluated: string[]`, `policies_evaluated: string[]`, `mode: Governance Mode`, `governance_context: string`, +1 more **Deep dive:** - docs/guides/HANDLER-PATTERNS-GUIDE.md — input handler patterns for governance flows ### Sponsored Intelligence #### `si_get_offering` Get offering details, availability, and optionally matching products before session handoff. **Request:** - Required: `offering_id: string` - Optional: `intent: string`, `context: Context`, `include_products: boolean`, `product_limit: integer` **Response (success branch):** - Required: `available: boolean` - Optional: `offering_token: string`, `ttl_seconds: integer`, `checked_at: string`, `offering: object`, `matching_products: object[]`, `total_matching: integer`, `unavailable_reason: string`, `alternative_offering_ids: string[]`, +2 more #### `si_initiate_session` Host initiates SI session with brand agent - includes context, identity, and capability negotiation. **Request:** - Required: `intent: string`, `identity: Si Identity`, `idempotency_key: string` - Optional: `context: Context`, `media_buy_id: string`, `placement: string`, `offering_id: string`, `supported_capabilities: Si Capabilities`, `offering_token: string` **Response (success branch):** - Required: `session_id: string`, `session_status: Si Session Status` - Optional: `response: object`, `negotiated_capabilities: Si Capabilities`, `session_ttl_seconds: integer`, `errors: object[]`, `context: Context` #### `si_send_message` Send a message within an active SI session. **Request:** - Required: `idempotency_key: string`, `session_id: string` - Optional: `message: string`, `action_response: object`, `context: Context` **Response (success branch):** - Required: `session_id: string`, `session_status: Si Session Status` - Optional: `response: object`, `mcp_resource_uri: string`, `handoff: object`, `errors: object[]`, `context: Context` #### `si_terminate_session` Terminate an SI session with reason (handoff_transaction, handoff_complete, user_exit, session_timeout, host_terminated). **Request:** - Required: `session_id: string`, `reason: 'handoff_transaction' | 'handoff_complete' | 'user_exit' | 'session_timeout' | 'host_terminated'` - Optional: `termination_context: object`, `context: Context` **Response (success branch):** - Required: `session_id: string`, `terminated: boolean` - Optional: `session_status: Si Session Status`, `acp_handoff: object`, `follow_up: object`, `errors: object[]`, `context: Context` **Deep dive:** - docs/guides/ASYNC-DEVELOPER-GUIDE.md — session lifecycle patterns ### Trusted Match (TMP) Real-time execution layer. These are HTTP operations, not MCP tools. #### `context_match` Evaluate available packages against content context. #### `identity_match` Evaluate user eligibility for packages using an opaque identity token. ## Common Flows These are the standard tool call sequences from the AdCP storyboards. Each flow shows the tools called in order. ### Brand **Brand baseline** — Baseline protocol storyboard — every brand agent must declare the brand protocol in capabilities and return a schema-valid brand identity. Flow: `get_adcp_capabilities → get_brand_identity` **Brand agent rejects rights acquisition when governance denies** — Verifies that a brand agent propagates GOVERNANCE_DENIED when the buyer's governance plan denies a rights license. Flow: `sync_plans → sync_accounts → sync_governance → get_rights → acquire_rights` ### Creative **Creative lifecycle** — Full creative lifecycle on a stateful platform: sync multiple creatives, list with filtering, build and preview across formats, observe status transitions. Flow: `get_adcp_capabilities → list_creative_formats → sync_creatives → list_creatives → preview_creative → build_creative` **Sales agent with creative capabilities** — Stateful sales agent that accepts pushed creative assets and renders them in its environment. Flow: `get_adcp_capabilities → list_creative_formats → sync_creatives → preview_creative` **Creative ad server** — Stateful ad server with pre-loaded creatives. Generates serving tags per media buy. Optionally bills through AdCP. Flow: `get_adcp_capabilities → list_creatives → list_creative_formats → build_creative → get_creative_delivery → report_usage` **Creative template and transformation agent** — Stateless creative agent that takes assets in, applies templates, and produces tags or rendered output. Flow: `get_adcp_capabilities → list_creative_formats → preview_creative → build_creative` ### Campaign Governance **Governance denial and human escalation** — Buyer's governance agent denies a media buy that exceeds spending authority, escalates to a human who approves with conditions. Flow: `get_adcp_capabilities → sync_accounts → sync_governance → sync_plans → get_products → check_governance → create_media_buy → report_plan_outcome → get_plan_audit_logs` **Campaign governance — delivery monitoring with drift detection** — Governance agent monitors delivery, detects budget drift past thresholds, and triggers re-evaluation. Flow: `get_adcp_capabilities → sync_plans → check_governance → create_media_buy → get_media_buy_delivery → check_governance` **Campaign governance — denied** — Governance agent denies a media buy that exceeds the agent's spending authority. No human escalation — the buy is blocked. Flow: `get_adcp_capabilities → sync_plans → check_governance` **Campaign governance — conditional approval** — Governance agent approves a media buy with conditions. Buyer re-checks after meeting the conditions. Flow: `get_adcp_capabilities → sync_plans → check_governance → create_media_buy` ### Media Buy **Media buy seller agent** — Seller agent that receives briefs, returns products, accepts media buys, and reports delivery. Flow: `get_adcp_capabilities → sync_accounts → sync_governance → get_products → list_creative_formats → create_media_buy → get_media_buys → list_creative_formats → sync_creatives → get_media_buy_delivery` **Seller returns submitted task envelope when create_media_buy goes async** — Verifies the AdCP-payload wire shape of the submitted-arm response from create_media_buy: status='submitted', task_id present, no media_buy_id and no packages on the envelope. Flow: `comply_test_controller → create_media_buy` **Creative lifecycle is decoupled from media buy lifecycle** — Validates that canceling a media buy releases package-creative assignments but leaves the underlying creatives in the library with their review state intact, and that buyers can reuse released creatives on a new buy. Flow: `get_products → create_media_buy → sync_creatives → list_creatives → update_media_buy → list_creatives → create_media_buy → sync_creatives` **Seller creates buy when governance approves** — Verifies that the seller creates a media buy when governance approves the transaction. Flow: `sync_plans → sync_accounts → sync_governance → get_products → create_media_buy` **Seller attaches conditions when governance approves with conditions** — Verifies that the seller attaches governance conditions to the buy when governance approves with conditions. Flow: `sync_plans → sync_accounts → sync_governance → get_products → create_media_buy` **Seller rejects buy when governance denies** — Verifies that the seller rejects a media buy and propagates the denial when governance denies the transaction. Flow: `sync_plans → sync_accounts → sync_governance → get_products → create_media_buy` **Seller accepts corrected buy after governance denial** — Verifies that a buyer can recover from GOVERNANCE_DENIED by shrinking the buy to within plan limits and retrying. Flow: `sync_plans → sync_accounts → sync_governance → get_products → create_media_buy` **Seller rejects illegal state transitions and unknown references** — Validates that the seller returns structured AdCP errors (MEDIA_BUY_NOT_FOUND, PACKAGE_NOT_FOUND, NOT_CANCELLABLE) rather than 500s or undefined behavior when the buyer references missing entities or attempts forbidden state transitions. Flow: `update_media_buy → get_products → create_media_buy → update_media_buy` **Seller handles property_list / collection_list references that match zero inventory** — Verifies a seller returns a zero-forecast product or a clear error — not a crash — when a buyer references inventory lists that resolve to nothing in the seller's catalog. Flow: `get_products → create_media_buy` **Seller honors property_list and collection_list targeting on create and update** — Verifies that a seller accepts PropertyListReference and CollectionListReference in package targeting on create_media_buy AND update_media_buy, with parity between both paths. Flow: `get_products → create_media_buy → get_media_buys → update_media_buy → get_media_buys` **Seller rejects unworkable measurement_terms** — Buyer proposes measurement_terms the seller will not accept; seller returns TERMS_REJECTED; buyer retries with seller-compatible terms. Flow: `get_products → create_media_buy` **Creative sync unblocks pending_creatives → pending_start** — Verifies that a media buy created without creatives sits in pending_creatives until sync_creatives completes, then transitions to pending_start. Flow: `get_products → create_media_buy → sync_creatives → update_media_buy → get_media_buys` **Seller handles proposal refinement and finalize** — Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and accept via create_media_buy. Flow: `sync_accounts → get_products → create_media_buy` **Seller handles product refinement** — Verifies that a media buy seller supports buying_mode: refine with product-level and request-level changes. Flow: `sync_accounts → get_products` **Media buy state machine lifecycle** — Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement. Flow: `get_adcp_capabilities → get_products → create_media_buy → update_media_buy` **Generative seller agent** — Seller agent that generates creatives from briefs at buy time — no pre-built assets required. Flow: `get_adcp_capabilities → sync_accounts → list_creative_formats → get_products → create_media_buy → sync_creatives → get_media_buy_delivery` **Governance-aware seller** — Seller agent that composes with a campaign-governance agent on the buyer side — accepts sync_governance, calls check_governance before confirming spend, and propagates governance approvals, conditions, and denials unchanged. Optional claim; pure sellers without governance composition do not claim this specialism and skip the governance scenarios as not_applicable. Flow: `get_adcp_capabilities` **Broadcast linear TV seller agent** — Seller agent for broadcast linear TV inventory — primetime and fringe spots with measurement windows, agency estimate numbers, Ad-ID-based creative sync, and delayed delivery reporting. Flow: `get_adcp_capabilities → get_products → create_media_buy → get_media_buys → list_creative_formats → sync_creatives → expect_webhook → get_media_buy_delivery` **Catalog-driven creative and conversion tracking** — Seller that renders dynamic ads from product catalogs, tracks conversions, and optimizes delivery based on performance feedback. Flow: `get_adcp_capabilities → sync_accounts → list_creative_formats → sync_catalogs → build_creative → expect_substitution_safe → get_products → create_media_buy → sync_event_sources → log_event → provide_performance_feedback → get_media_buy_delivery` **Guaranteed media buy with human IO approval** — Seller agent that requires human-in-the-loop IO signing before guaranteed media buys go live. Flow: `get_adcp_capabilities → sync_accounts → get_products → create_media_buy → get_media_buys → sync_creatives → get_media_buy_delivery` **Non-guaranteed auction-based media buy** — Seller agent for auction-based, non-guaranteed buying where the buyer sets bid prices and budgets. Flow: `get_adcp_capabilities → get_products → create_media_buy → get_media_buys → update_media_buy → get_media_buy_delivery` **Media buy via proposal acceptance** — Seller agent that generates curated media plan proposals the buyer can review, refine, and accept. Flow: `get_adcp_capabilities → sync_accounts → get_products → create_media_buy → list_creative_formats → sync_creatives → get_media_buy_delivery` ### Reporting **Seller returns valid delivery reporting** — Verifies that get_media_buy_delivery returns schema-compliant delivery data after simulated delivery via the test controller. Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery` ### Signals **Signals baseline** — Baseline domain storyboard — every signals agent must discover signals and return an activation, regardless of whether they are owned or marketplace. Flow: `get_adcp_capabilities → get_signals → activate_signal` **Marketplace signal agent** — Signal agent that resells third-party data provider signals with verifiable catalog provenance. Flow: `get_adcp_capabilities → get_signals → activate_signal` **Signal agent rejects activation when governance denies** — Verifies that a signal agent propagates GOVERNANCE_DENIED when the buyer's governance plan denies activation. Flow: `sync_plans → sync_accounts → sync_governance → get_signals → activate_signal` **Owned signal agent** — Signal agent serving first-party or proprietary audience data without external catalog verification. Flow: `get_adcp_capabilities → get_signals → activate_signal` **get_signals pagination cursor integrity** — Validates the cursor↔has_more invariant on a paginated get_signals response by walking from a continuation page to the next page under a broad query. Flow: `get_adcp_capabilities → get_signals` ### Sponsored Intelligence (SI) **Sponsored intelligence baseline** — Baseline domain storyboard — every SI agent must discover offerings, initiate a session, exchange messages, and terminate cleanly. Flow: `get_adcp_capabilities → si_get_offering → si_initiate_session → si_send_message → si_terminate_session` ### Audiences **Audience sync** — Full audience lifecycle: account discovery, audience creation with hashed identifiers, and audience deletion. Flow: `get_adcp_capabilities → list_accounts → sync_audiences` **Social platform** — Social media platform that accepts audience segments, native creatives, and conversion events from buyer agents. Flow: `get_adcp_capabilities → sync_accounts → list_accounts → sync_audiences → sync_creatives → sync_catalogs → sync_creatives → sync_event_sources → log_event → get_account_financials` ### Core **Brand identity and rights licensing** — Brand agent that serves identity assets and licenses rights for AI-generated content. Flow: `get_adcp_capabilities → get_brand_identity → get_rights → acquire_rights` **Capability discovery** — Buyer calls get_adcp_capabilities to discover what an agent supports before making any buying or creative decisions. Flow: `get_adcp_capabilities` **Pagination cursor integrity — list_collection_lists** — Validates the cursor↔has_more invariant by walking a paginated list_collection_lists response from a continuation page to a terminal page. Flow: `get_adcp_capabilities → create_collection_list → list_collection_lists` **Pagination cursor integrity — list_content_standards** — Validates the cursor↔has_more invariant by walking a paginated list_content_standards response from a continuation page to a terminal page. Flow: `get_adcp_capabilities → create_content_standards → list_content_standards` **Deterministic testing** — Uses comply_test_controller to force state transitions and simulate delivery/budget, verifying state machines and reporting. Flow: `get_adcp_capabilities → comply_test_controller → sync_accounts → list_accounts → comply_test_controller → create_media_buy → comply_test_controller → get_media_buys → comply_test_controller → sync_creatives → comply_test_controller → sync_creatives → comply_test_controller → si_initiate_session → comply_test_controller → si_send_message → create_media_buy → comply_test_controller → get_media_buy_delivery → create_media_buy → comply_test_controller` **Pagination shape — get_media_buys** — Validates that get_media_buys responses carry a well-formed pagination envelope honoring the cursor↔has_more invariant. Flow: `get_adcp_capabilities → get_media_buys` **Idempotency enforcement** — Validates that mutating requests enforce idempotency_key — replays return cached responses, key reuse with a different payload returns IDEMPOTENCY_CONFLICT, and fresh keys create new resources. Flow: `get_adcp_capabilities → create_media_buy → expect_webhook → create_media_buy → get_media_buys` **Pagination cursor integrity — list_creative_formats** — Validates the cursor↔has_more invariant on list_creative_formats by seeding two formats and walking pages with max_results=1. Flow: `get_adcp_capabilities → comply_test_controller → list_creative_formats` **Pagination cursor integrity — list_accounts** — Validates the cursor↔has_more invariant by walking a paginated list_accounts response from a continuation page to a terminal page. Flow: `get_adcp_capabilities → sync_accounts → list_accounts` **Pagination cursor integrity** — Validates the cursor↔has_more invariant by walking a paginated list_creatives response from a continuation page to a terminal page. Flow: `get_adcp_capabilities → list_creatives` **Pagination cursor integrity — list_property_lists** — Validates the cursor↔has_more invariant by walking a paginated list_property_lists response from a continuation page to a terminal page. Flow: `get_adcp_capabilities → create_property_list → list_property_lists` **Schema compliance and temporal validation** — Validates that agent responses conform to AdCP schemas and that temporal constraints are enforced. Flow: `get_adcp_capabilities → get_products → list_creative_formats → create_media_buy` **v3 envelope integrity — no legacy status fields** — v3 protocol envelopes MUST NOT carry task_status or response_status — v2 legacy field names that have no semantics in v3. Flow: `get_adcp_capabilities` **Webhook emission — outbound webhook conformance (signing + idempotency)** — Any agent that emits webhooks MUST carry a stable idempotency_key across retries and — when the buyer has not opted into the deprecated HMAC fallback — MUST sign deliveries under the RFC 9421 webhook profile. Graded by a runner hosting a webhook receiver during storyboard execution. Flow: `get_adcp_capabilities → expect_webhook → expect_webhook_retry_keys_stable → expect_webhook_signature_valid` ### Governance **Collection lists** — Curated collection lists for program-level brand safety and content targeting — create, query, update, and delete lists of content programs (shows, series, podcasts). Flow: `get_adcp_capabilities → create_collection_list → list_collection_lists → get_collection_list → update_collection_list → delete_collection_list` **Content standards** — Define creative quality rules, calibrate content against them, and validate that delivered ads met the standards. Flow: `get_adcp_capabilities → create_content_standards → list_content_standards → get_content_standards → update_content_standards → calibrate_content → update_content_standards → calibrate_content → validate_content_delivery` **Property lists** — Curated property lists for inventory grouping, targeting governance, and delivery compliance — create, query, update, delete, and validate. Flow: `get_adcp_capabilities → create_property_list → list_property_lists → get_property_list → update_property_list → validate_property_delivery → delete_property_list` ### **Generative creative agent** — Agent that takes a brief and generates finished creatives from scratch — no input assets required. Flow: `get_adcp_capabilities → list_creative_formats → build_creative → sync_catalogs → build_creative → sync_catalogs → build_creative → expect_substitution_safe` **Runner output contract** — Required failure-detail shape that AdCP storyboard runners MUST emit so implementors can self-diagnose validation failures. ### Error Handling **Error handling and compliance** — Validates that agents return properly structured AdCP errors with correct codes, recovery hints, and transport bindings. Flow: `get_adcp_capabilities → create_media_buy → get_products → create_media_buy → get_products → create_media_buy` ### Security **Authentication baseline** — Every AdCP agent MUST require authentication on protected operations. At least one of API key or OAuth MUST be implemented and correctly advertised. ### Security transport **Signed requests — RFC 9421 transport-layer verification** — Agent verifies RFC 9421 HTTP Signatures on incoming AdCP requests per the transport-layer profile. Universal capability-gated storyboard — runs for any agent advertising `request_signing.supported: true` regardless of `supported_protocols`. Graded against conformance vectors covering every checklist step and canonicalization-edge rule. Flow: `get_adcp_capabilities` ## Error Codes Agents use the `recovery` classification to decide what to do: `transient` → retry after delay, `correctable` → fix parameters and retry, `terminal` → stop and report. | Code | Recovery | Description | |------|----------|-------------| | `ACCOUNT_AMBIGUOUS` | correctable | Natural key resolves to multiple accounts. | | `ACCOUNT_NOT_FOUND` | terminal | The account reference could not be resolved. | | `ACCOUNT_PAYMENT_REQUIRED` | terminal | Account has an outstanding balance requiring payment before new buys. | | `ACCOUNT_SETUP_REQUIRED` | correctable | Natural key resolved but the account needs setup before use. | | `ACCOUNT_SUSPENDED` | terminal | Account has been suspended. | | `AUDIENCE_TOO_SMALL` | correctable | Audience segment is below the minimum required size for targeting. | | `AUTH_REQUIRED` | correctable | Authentication is required, or presented credentials were rejected. Two operational sub-cases share this code: (a) credentials missing — agent provides credentials and retries; (b) credentials presented but rejected (expired / revoked / malformed signature) — agent SHOULD NOT auto-retry, since re-presenting a rejected credential against an SSO endpoint creates retry-storm patterns indistinguishable from brute-force probes. In sub-case (b) the agent SHOULD escalate to operator for credential rotation rather than loop. A future minor release splits this code into AUTH_MISSING (correctable) and AUTH_INVALID (terminal); agents handling 3.0.x sellers SHOULD apply the same operational distinction at the application layer. | | `BUDGET_EXCEEDED` | correctable | Operation would exceed the allocated budget for the media buy or package. Distinct from BUDGET_EXHAUSTED (already spent) and BUDGET_TOO_LOW (below minimum). | | `BUDGET_EXHAUSTED` | terminal | Account or campaign budget has been fully spent. Distinct from BUDGET_TOO_LOW (rejected at submission). | | `BUDGET_TOO_LOW` | correctable | Budget is below the seller's minimum. | | `CAMPAIGN_SUSPENDED` | transient | Campaign governance has been suspended pending human review; the governance agent MUST reject `check_governance` and `report_plan_outcome` calls on the affected plan until the escalation is resolved. Distinct from `ACCOUNT_SUSPENDED` (account-wide) — this is scoped to a single plan/campaign. | | `COMPLIANCE_UNSATISFIED` | correctable | A required disclosure from the brief's compliance section cannot be satisfied by the target format — either the required position or the required persistence mode is not in the format's disclosure_capabilities. | | `CONFLICT` | transient | Concurrent modification detected. The resource was modified by another request between read and write. | | `CREATIVE_DEADLINE_EXCEEDED` | correctable | Creative change submitted after the package's creative_deadline. Distinct from CREATIVE_REJECTED (content policy failure). | | `CREATIVE_NOT_FOUND` | correctable | Referenced creative does not exist in the agent's creative library. Sellers MUST return this code uniformly for any creative_id not owned by the calling account — never distinguish 'exists in another tenant' from 'does not exist', which would enable cross-tenant enumeration. | | `CREATIVE_REJECTED` | correctable | Creative failed content policy review. For deadline violations, see CREATIVE_DEADLINE_EXCEEDED. | | `GOVERNANCE_DENIED` | correctable | A registered governance agent denied the transaction. Sellers MUST place the denial in the task's structured rejection arm when one exists (e.g., `acquire_rights` → `AcquireRightsRejected`, `creative_approval` → `CreativeRejected`); otherwise in `errors[]` + `adcp_error`. Buyers MUST dispatch on the response's discriminated `status` first and fall back to `errors[].code` / `adcp_error.code` only when no rejection arm exists for that task. The buyer may restructure the buy (e.g., reduce budget, split into smaller transactions), escalate to human spending authority, or contact the governance agent for details. Wire placement (full guidance). Governance denial is a structured business outcome, not a system error — the governance call SUCCEEDED and the agent returned a denial verdict. Two cases: 1. Task response defines a structured rejection arm. The arm IS the canonical denial shape. The seller populates `reason` (human-readable, propagating governance findings) and `suggestions` (optional) and does NOT additionally emit `GOVERNANCE_DENIED` in `errors[]` or `adcp_error`. The rejection arms enforce this at the schema layer: e.g., `AcquireRightsRejected` and `CreativeRejected` both declare `not: { required: [errors] }`, so dual-emission is already a schema violation. The code does not appear on the wire when the rejection arm is used. Transport-level success markers MUST NOT be flipped (HTTP 200, MCP `isError: false`, A2A `succeeded`) — the task ran successfully and produced a structured response. 2. Task response has no rejection arm (e.g., `create_media_buy` returns Success / Error / Submitted arms only). The seller populates `errors[].code: GOVERNANCE_DENIED` in the payload AND `adcp_error.code: GOVERNANCE_DENIED` on the envelope per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Transport-level failure markers DO flip in this case (HTTP 4xx, MCP `isError: true`, A2A `failed`) — the task could not produce a success artifact. The rule generalizes to any current or future task whose response defines a discriminated rejection arm. In either placement, sellers SHOULD propagate governance findings verbatim — buyers' recovery decisions depend on what specifically was rejected. `GOVERNANCE_DENIED` is reserved for verdicts received from a reachable governance agent; if the governance call itself failed (timeout, network, config error), use `GOVERNANCE_UNAVAILABLE` instead. | | `GOVERNANCE_UNAVAILABLE` | transient | A registered governance agent is unreachable. Sellers MUST place this code in `errors[]` + `adcp_error` (never a structured rejection arm) and flip transport-level failure markers (HTTP 5xx, MCP `isError: true`, A2A `failed`). Distinct from `GOVERNANCE_DENIED` (agent reachable and explicitly denied — see that code's wire-placement guidance). Wire placement (full guidance). Governance unavailability is a system error — the governance call FAILED (timeout, network, config error) and the seller could not get a verdict at all. Always populate both layers per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Do NOT use a structured rejection arm for unavailability even when the task offers one — the buyer's recovery semantics differ (retry-with-backoff for unavailability vs. restructure-or-escalate for denial), and conflating them masks the system-error signal. | | `IDEMPOTENCY_CONFLICT` | correctable | An earlier request with the same idempotency_key was processed with a different canonical payload within the seller's replay window. Distinct from CONFLICT (concurrent write) — this indicates the client reused a key across semantically different requests. | | `IDEMPOTENCY_EXPIRED` | correctable | The idempotency_key was seen previously but its cached response has been evicted because it is past the seller's declared replay_ttl_seconds. Distinct from IDEMPOTENCY_CONFLICT (different payload within window) — this indicates the retry arrived too late for at-most-once guarantees. If the buyer has any evidence the prior call succeeded (partial response received before crash, entry in the buyer's own DB, a webhook fired), the buyer MUST do the natural-key check BEFORE minting a new key — minting a new key in that situation is exactly how double-creation happens. | | `INVALID_REQUEST` | correctable | Request is malformed, missing required fields, or violates schema constraints. | | `INVALID_STATE` | correctable | Operation is not permitted for the resource's current status (e.g., updating a completed or canceled media buy, or modifying a canceled package). | | `IO_REQUIRED` | correctable | The committed proposal requires a signed insertion order but no io_acceptance was provided. | | `MEDIA_BUY_NOT_FOUND` | correctable | Referenced media buy does not exist or is not accessible to the requesting agent. | | `NOT_CANCELLABLE` | correctable | The media buy or package cannot be canceled in its current state. The seller may have contractual or operational constraints that prevent cancellation. | | `PACKAGE_NOT_FOUND` | correctable | Referenced package does not exist within the specified media buy. | | `PERMISSION_DENIED` | correctable | The authenticated caller is not authorized for the requested action under the seller's own policies, or a required signed credential (e.g., a `governance_context` token on a spend-commit) is missing, fails verification, or was issued for a different plan, seller, or phase. Distinct from `AUTH_REQUIRED` (no credentials presented) and `GOVERNANCE_DENIED` (governance agent denied). | | `PLAN_NOT_FOUND` | correctable | Referenced governance plan does not exist or is not accessible to the requesting agent. Sellers MUST return this code uniformly for any plan_id not accessible to the calling account — never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration of governance plans. | | `POLICY_VIOLATION` | correctable | Request violates the seller's content or advertising policies. | | `PRODUCT_EXPIRED` | correctable | One or more referenced products have passed their expires_at timestamp and are no longer available for purchase. | | `PRODUCT_NOT_FOUND` | correctable | One or more referenced product IDs are unknown or expired. | | `PRODUCT_UNAVAILABLE` | correctable | The requested product is sold out or no longer available. | | `PROPOSAL_EXPIRED` | correctable | A referenced proposal ID has passed its expires_at timestamp. | | `PROPOSAL_NOT_COMMITTED` | correctable | The referenced proposal has proposal_status 'draft' and cannot be used to create a media buy. | | `RATE_LIMITED` | transient | Request rate exceeded. Retry after the retry_after interval. | | `REFERENCE_NOT_FOUND` | correctable | Generic fallback for a referenced identifier, grant, session, or other resource that does not exist or is not accessible by the caller. Use when no resource-specific not-found code applies (e.g., property lists, content standards, rights grants, SI offerings, proposals, catalogs, event sources, collection lists, brands, individual properties). Typed parameters that lack a dedicated standard code MUST also use REFERENCE_NOT_FOUND rather than minting a custom *_NOT_FOUND code. See 'Uniform response for inaccessible references' in error-handling.mdx for the full MUST list. Summary of the uniform-response MUST: sellers MUST return the same response for 'exists but the caller lacks access' as for 'does not exist' across every observable channel — error.code/message/field/details (message MUST be generic; error.field MUST be identical across both cases on typed parameters); HTTP status, A2A task.status.state, and MCP isError; response headers (ETag, Cache-Control, per-type rate-limit buckets, CDN tags); side effects (webhook/audit writes, background-job enqueues, per-type quota counters, DB-shard routing); and observability (logs, APM spans, third-party error telemetry like Sentry/Datadog). Sellers MUST perform the same resolution-and-authorization work on both paths (resolve-then-authorize; on true-miss still run an authorization decision of equivalent shape against an empty principal set so authorizer latency is not a side channel). Cache population MUST NOT be gated on authorization. Polymorphism is evaluated against the tool-schema's declared parameter shape before any lookup, and a tool's declared shape MUST be identical across all callers. | | `REQUOTE_REQUIRED` | correctable | An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can autonomously re-discover. | | `SERVICE_UNAVAILABLE` | transient | Seller service is temporarily unavailable. Retry with exponential backoff. | | `SESSION_NOT_FOUND` | correctable | SI session ID is invalid, expired, or does not exist. | | `SESSION_TERMINATED` | correctable | SI session has already been terminated and cannot accept further messages. | | `SIGNAL_NOT_FOUND` | correctable | Referenced signal does not exist in the agent's catalog. Sellers MUST return this code uniformly for any signal_id not accessible to the calling account — never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration. | | `TERMS_REJECTED` | correctable | Buyer-proposed measurement_terms were rejected by the seller. The error details SHOULD identify which specific term was rejected and the seller's acceptable range or supported vendors. | | `UNSUPPORTED_FEATURE` | correctable | A requested feature or field is not supported by this seller. | | `VALIDATION_ERROR` | correctable | Request contains invalid field values or violates business rules beyond schema validation. | | `VERSION_UNSUPPORTED` | correctable | The declared adcp_major_version is not supported by this seller. | Unknown codes: fall back to the HTTP status code (4xx = correctable, 5xx = transient). ## Test Scenarios Run compliance tests with `adcp test `. 24 built-in scenarios: | Scenario | What it tests | |----------|---------------| | `health_check` | Basic connectivity check - verify agent responds | | `discovery` | Test get_products, list_creative_formats, list_authorized_properties | | `create_media_buy` | Discovery + create a test media buy (sandbox) | | `full_sales_flow` | Full lifecycle: discovery → create → update → delivery | | `creative_sync` | Test sync_creatives flow | | `creative_inline` | Test inline creatives in create_media_buy | | `creative_flow` | Creative agent: list_formats → build → preview | | `signals_flow` | Signals agent: get_signals → activate | | `error_handling` | Verify agent returns proper error responses | | `validation` | Test schema validation (invalid inputs should be rejected) | | `pricing_edge_cases` | Test auction vs fixed pricing, min spend, bid_price | | `temporal_validation` | Test date/time ordering and format validation | | `behavior_analysis` | Analyze agent behavior: auth, brief relevance, filtering | | `response_consistency` | Check for schema errors, pagination bugs, data mismatches | | `capability_discovery` | Test get_adcp_capabilities and verify v3 protocol support | | `governance_property_lists` | Test property list CRUD (create, get, update, delete) | | `governance_content_standards` | Test content standards listing and calibration | | `si_session_lifecycle` | Test full SI session: initiate → messages → terminate | | `si_availability` | Quick check for SI offering availability | | `campaign_governance` | Full governance lifecycle: sync_plans → check → execute → report | | `campaign_governance_denied` | Denied flow: over-budget, unauthorized market | | `campaign_governance_conditions` | Conditions flow: apply conditions → re-check | | `campaign_governance_delivery` | Delivery monitoring with drift detection | | `seller_governance_context` | Verify seller persists governance_context from media buy lifecycle | **Deep dive:** Storyboard YAML definitions live at `https://adcontextprotocol.org/compliance/{version}/` and are mirrored locally in `compliance/cache/{version}/` after `npm run sync-schemas`. **Fictional entities:** `compliance/cache/{version}/universal/fictional-entities.yaml` defines all fictional companies used in storyboards and training (advertisers, agencies, publishers, data providers). Aligned to the character bible at docs.adcontextprotocol.org/specs/character-bible. All domains use the `.example` TLD. Sandbox brands (advertisers) are resolvable via AgenticAdvertising.org. ### Seeding fixtures for compliance (seller-side) Group A storyboards seed fixtures via `comply_test_controller.seed_product` (and the other `seed_*` scenarios) before calling the spec tool. Two SDK helpers bridge this: - **`mergeSeedProduct`** (plus `mergeSeedPricingOption`, `mergeSeedCreative`, `mergeSeedPlan`, `mergeSeedMediaBuy`): permissive merge of a sparse storyboard fixture onto the seller's baseline defaults. `undefined`/`null` keep base; arrays replace by default; well-known id-keyed lists (`pricing_options`, `publisher_properties`, `packages`, `assets`, plan `findings`) overlay by id so seeding one entry doesn't drop the rest. - **`bridgeFromTestControllerStore(store, productDefaults)`**: wires a `Map` seed store into `get_products` responses automatically. Sandbox requests merge seeded + handler products (seeded wins collisions); production traffic (no sandbox marker, or a resolved non-sandbox account) skips the bridge. Wire on `createAdcpServerFromPlatform(platform, { testController: bridgeFromTestControllerStore(store, baseline) })`. See `skills/build-seller-agent/SKILL.md` for the full pattern alongside `createComplyController`. ### Anti-façade upstream-traffic recording (`@adcp/sdk/upstream-recorder`) Storyboards declaring `check: upstream_traffic` (runner-output-contract v2.0.0, spec PR adcontextprotocol/adcp#3816) verify that an adapter actually called its upstream platform with the storyboard-supplied identifiers — distinguishing a real adapter from one returning shape-valid AdCP responses without touching upstream. Adopters opt in by advertising `query_upstream_traffic` on their `comply_test_controller`. `@adcp/sdk/upstream-recorder` is the producer-side reference middleware: a sandbox-only-by-default helper that wraps the adapter's HTTP layer with per-principal isolation, record-time secret redaction, ring-buffer + TTL eviction, and a `query()` method that maps onto the controller wire shape via `toQueryUpstreamTrafficResponse()`. Wire-up is four steps — boot recorder, wrap fetch, scope handlers in `runWithPrincipal`, return `toQueryUpstreamTrafficResponse(recorder.query(...))` from your `comply_test_controller`'s `query_upstream_traffic` scenario. Worked example at `examples/hello_signals_adapter_marketplace.ts`. See `skills/build-seller-agent/SKILL.md` § "Opting into `upstream_traffic`" for the full pattern, including multi-tenant principal resolution. ## Key Types See docs/TYPE-SUMMARY.md for field-level detail. Key types at a glance: | Type | Purpose | |------|---------| | `AgentConfig` | Agent connection config (uri, protocol, auth) | | `TaskResult` | Return type of every tool call (status + data/error/adcpError/correlationId/deferred/submitted) | | `InputHandler` | Callback for agent clarification requests | | `ConversationContext` | Passed to InputHandler with messages, question, helpers | | `Product` | Advertising inventory item with formats, pricing, targeting | | `MediaBuy` | Purchased campaign with packages, budget, schedule | | `CreativeAsset` | Creative with type, format, dimensions, status | | `Targeting` | Audience criteria (geo, demo, behavioral, contextual, device) | | `PricingOption` | Price model (CPM, vCPM, CPC, CPCV, CPV, CPP, CPA, FlatRate, Time) | | `GovernanceConfig` | Buyer-side governance middleware config | ## Task Statuses Every tool call returns a `TaskResult` with one of these statuses: - `completed` — Success. Data in `result.data`. - `input-required` — Agent needs clarification. Use `InputHandler` or `result.deferred.resume(answer)`. - `submitted` — Long-running. Poll via `result.submitted.waitForCompletion()` or use webhooks. - `working` — In progress (intermediate, usually not seen by callers). - `deferred` — Requires human decision. Token in `result.deferred.token`. - `governance-denied` — Blocked by governance middleware. **Deep dive:** docs/guides/ASYNC-DEVELOPER-GUIDE.md, docs/guides/ASYNC-API-REFERENCE.md ## Protocols AdCP tools are served over MCP (Model Context Protocol) or A2A (Agent-to-Agent). The client auto-detects based on `AgentConfig.protocol`. MCP endpoints end with `/mcp/`. Auth is via bearer token in `x-adcp-auth` header. **Deep dive:** docs/development/PROTOCOL_DIFFERENCES.md ## Discovery Publishers declare agents in `/.well-known/adagents.json`. Brands declare identity in `/.well-known/brand.json`. Use `PropertyCrawler` or `adcp registry` CLI to discover agents. ## Where to Read More These docs are available locally in the repo and hosted at https://adcontextprotocol.github.io/adcp-client/ | Need | Local path | Hosted | |------|-----------|--------| | Full type signatures | docs/TYPE-SUMMARY.md | [link](https://adcontextprotocol.github.io/adcp-client/TYPE-SUMMARY.md) | | Getting started / install | docs/getting-started.md | [link](https://adcontextprotocol.github.io/adcp-client/getting-started.md) | | Build a server-side agent | docs/guides/BUILD-AN-AGENT.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/BUILD-AN-AGENT.md) | | Migrating 6.7 → 6.9 (skips deprecated 6.8.0; 13 additive recipes; 2 breaking) | docs/migration-6.7-to-6.9.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-6.7-to-6.9.md) | | Migrating 6.6 → 6.7 (15 recipes; 2 breaking) | docs/migration-6.6-to-6.7.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-6.6-to-6.7.md) | | Migrating 5.x → 6.x | docs/migration-5.x-to-6.x.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-5.x-to-6.x.md) | | BuyerAgentRegistry adopter migration | docs/migration-buyer-agent-registry.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-buyer-agent-registry.md) | | Account resolution: explicit / implicit / derived | docs/guides/account-resolution.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/account-resolution.md) | | ctx_metadata credential safety | docs/guides/CTX-METADATA-SAFETY.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/CTX-METADATA-SAFETY.md) | | Request signing (RFC 9421) + JWKS | docs/guides/SIGNING-GUIDE.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/SIGNING-GUIDE.md) | | Conformance (property-based fuzzing) | docs/guides/CONFORMANCE.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/CONFORMANCE.md) | | Validate your agent (5-command checklist) | docs/guides/VALIDATE-YOUR-AGENT.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/VALIDATE-YOUR-AGENT.md) | | Async patterns (polling, webhooks, deferred) | docs/guides/ASYNC-DEVELOPER-GUIDE.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/ASYNC-DEVELOPER-GUIDE.md) | | Async API reference | docs/guides/ASYNC-API-REFERENCE.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/ASYNC-API-REFERENCE.md) | | Input handler patterns | docs/guides/HANDLER-PATTERNS-GUIDE.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/HANDLER-PATTERNS-GUIDE.md) | | Webhook configuration | docs/guides/PUSH-NOTIFICATION-CONFIG.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/PUSH-NOTIFICATION-CONFIG.md) | | Real-world code examples | docs/guides/REAL-WORLD-EXAMPLES.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/REAL-WORLD-EXAMPLES.md) | | CLI reference | docs/CLI.md | [link](https://adcontextprotocol.github.io/adcp-client/CLI.md) | | Zod runtime validation | docs/ZOD-SCHEMAS.md | [link](https://adcontextprotocol.github.io/adcp-client/ZOD-SCHEMAS.md) | | Testing strategy | docs/guides/TESTING-STRATEGY.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/TESTING-STRATEGY.md) | | Testing `composeMethod`-wrapped handlers | docs/recipes/composeMethod-testing.md | [link](https://adcontextprotocol.github.io/adcp-client/recipes/composeMethod-testing.md) | | Protocol differences (MCP vs A2A) | docs/development/PROTOCOL_DIFFERENCES.md | [link](https://adcontextprotocol.github.io/adcp-client/development/PROTOCOL_DIFFERENCES.md) | | TypeDoc API reference | docs/api/index.html | [link](https://adcontextprotocol.github.io/adcp-client/api/index.html) | JSON schemas (source of truth): `schemas/cache/latest/index.json` (local only) ## External Resources - Documentation: https://adcontextprotocol.github.io/adcp-client/ - npm: https://www.npmjs.com/package/@adcp/sdk - Spec: https://adcontextprotocol.org - CLI: `npx @adcp/sdk@latest`