llms.txt@docs · git:20260914.ecf1c74 · 2026-09-14 · sha256 6ea7db5ab1c117b8

llms.txt@docs git:20260914.ecf1c74B

Immutable. This exact content is served forever at /api/v1/blob/6ea7db5ab1c117b8.

# Ad Context Protocol (AdCP)

> Generated at: 2026-09-14
> Library: @adcp/sdk v14.0.0-rc.36
> 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.
> Note: generated error-code prose may include explicit SDK compatibility overlays applied by `scripts/lib/error-code-prose-overlays.ts` when bundled beta manifest wording lags SDK behavior.

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

## Start here: SDK 14 and AdCP 3.2

SDK 14 requires Node.js `^20.19.0 || >=22.12.0`; install the newest v14 prerelease with `@adcp/sdk@^14.0.0-0`.

SDK 14 is compact-lifecycle first: `list_products → buy_products → control_media_buy`, with `request_proposals → refine_proposals → accept_proposal` when terms need negotiation.

- **Buyer** (calling a seller): read `docs/guides/BUYER-QUICKSTART-3.2.md` first.
- **Before proposal acceptance:** use `verifyProposalCommercialTerms` from `@adcp/sdk/negotiation/verification` with a complete, independently reviewed snapshot and the seller-served schema version. Never use an unreviewed candidate as its own expected terms. See `docs/guides/PROPOSAL-TERMS-VERIFICATION.md`.
- **Seller** (implementing an agent that others call): read `docs/guides/SELLER-QUICKSTART-3.2.md` first, then `docs/guides/BUILD-AN-AGENT.md` for the complete framework surface.
- **Upgrading an existing application:** read `docs/migration-13-to-14.md`; established 3.0/3.1 tools remain supported as an explicit compatibility path.

## Server framework reference

```typescript
import { serve } from '@adcp/sdk';
import {
  createAdcpServerFromPlatform,
  createIdempotencyStore,
  definePlatform,
  defineSignalsPlatform,
  memoryBackend,
} from '@adcp/sdk/server';

// Single-process example. Use pgBackend(pool) or redisBackend(client) in production.
const idempotency = createIdempotencyStore({ backend: memoryBackend(), ttlSeconds: 86400 });

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',
  idempotency,
})); // http://localhost:3001/mcp
```

Compile-time enforcement: `RequiredPlatformsFor<S>` 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, and status normalization are framework-owned. Under AdCP 3.2, synchronous terminal responses remain silent on the task-webhook channel; the deprecated `autoEmitCompletionWebhooks` option is ignored.

Standing caller- and account-level notification subscribers require a durable control plane in addition to webhook delivery. Use `createPostgresPersistentNotificationRuntime()` for declarative replacement, exact-tuple proof generations, write-only credential bindings, anchor-safe fanout, and live authorization before every attempt/retry. See `docs/guides/PERSISTENT-NOTIFICATION-RUNTIME.md`; do not implement a second subscriber writer behind the raw `syncAgentNotificationConfigs` hook.

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<Meta> & SalesIngestionPlatform<Meta> = { ... }`) don't need them.

**Typed errors instead of `new AdcpError(code, ...)`.** `AuthMissingError`, `AuthInvalidError`, `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. `AuthRequiredError` remains as a deprecated `AUTH_REQUIRED` compatibility wrapper for older sellers; new seller code should use the split auth classes. Each maps to its wire error code with `recovery` baked in. Throw from platform methods. In `accounts.resolve`, use auth errors only for inbound authentication failures; missing sync linkage or unknown account references should stay `ACCOUNT_NOT_FOUND` / `null`.

**`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'`, an upstream-managed account-id namespace — the platform you front owns the roster (Meta / Snap ad accounts, audiostack, flashtalking, single-namespace retail-media). Buyers discover ids through `list_accounts` and send `account: { account_id }`; the framework refuses the `{ brand, operator }` arm for this mode and `accounts.list` is required (`createAdcpServerFromPlatform` throws `PlatformConfigError` without it). Provide `toAccount(ctx)` when a credential reaches exactly one account or `listAccounts(ctx)` when it reaches many (plus optional `lookupAccount(id, ctx)` for large rosters); the factory verifies buyer-supplied `account_id` against what the caller's credential can reach, returns `null` on a miss, auto-selects the account on ref-less tools only when exactly one is reachable, wires a filtered and paged `list_accounts`, and still emits legacy-compatible `AUTH_REQUIRED` on missing-credential calls. The framework backstops hand-rolled `'derived'` stores: a resolved account whose `id` isn't the one the buyer named is refused with `ACCOUNT_NOT_FOUND`, and `sync_accounts` / `sync_governance` entries are resolved against the caller's reachable set before any write. Buyer code must continue to handle `AUTH_REQUIRED` alongside `AUTH_MISSING` / `AUTH_INVALID`. Changed in SDK 14 (adcp-client#1647 / adcp#5062) — Shape D previously refused inline `account_id` and was documented as single-tenant-only. All four live at `@adcp/sdk/server`.

**Stateless BYOK provider auth.** For single-account API-key or bearer-token BYOK, the provider credential can be the AdCP request credential for that endpoint: `Authorization: Bearer <provider_api_key_or_access_token>`. This keeps the baseline seller-agent wrapper pattern single-plane: the seller agent authenticates the request with the caller-presented provider credential, derives the account from request auth, and uses the same request-local token for upstream provider calls. No SDK-managed OAuth flow, refresh-token store, provider-token store, or callback route is required when the caller owns the provider credential lifecycle. If the provider credential can see multiple upstream accounts, stay in `'derived'` and supply `listAccounts` so buyers can discover and name one, or use `createOAuthPassthroughResolver` under `'explicit'` when you own the id namespace. Handlers with a resolved account should read the active token from `ctx.account.authInfo?.token`; refresh hooks update `account.authInfo`. Handlers without a resolved account can read the request token from `ctx.authInfo.token`. Use a stable non-secret identity such as `ctx.authInfo.credential.key_id`, `ctx.authInfo.credential.client_id`, or an adopter-supplied `principal` string for cache/idempotency scoping. Treat both token paths as request-local: do not copy provider tokens into persisted Account rows, `ctx_metadata`, `ctx.authInfo.extra`, request `ext` / body fields, or log lines. Add a separate provider-auth channel only for dual-auth proxy deployments where one request carries both caller-to-agent auth and a distinct upstream-provider credential.

**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<Meta>` field annotations claiming `sales-non-guaranteed` / `-guaranteed` / `-broadcast-tv` / `-catalog-driven` need to switch the annotation to `: SalesCorePlatform<Meta> & SalesIngestionPlatform<Meta>` (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<TCtxMeta>` 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<TCtxMeta>`** is the request-context type for tools whose wire request doesn't carry an `account` field (`previewCreative`, `listCreativeFormats`, `providePerformanceFeedback`); `ctx.account` is `Account<TCtxMeta> | 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 (Buyer)

```typescript
import { randomUUID } from 'node:crypto';
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');

const account = { account_id: 'seller-issued-account-id' };
const listed = await agent.listProducts({
  account,
  brand: { domain: 'advertiser.example' },
});
if (!listed.success || listed.status !== 'completed') throw new Error(listed.error ?? listed.status);

const product = listed.data!.products[0];
const pricing = product?.pricing_options?.[0];
if (!product || !pricing) throw new Error('Seller returned no purchasable products');

const purchaseIdempotencyKey = randomUUID(); // Persist before sending; reuse after an ambiguous timeout.
const endTime = new Date(Date.now() + 30 * 24 * 60 * 60 * 1_000).toISOString();
const bought = await agent.buyProducts({
  idempotency_key: purchaseIdempotencyKey,
  account,
  brand: { domain: 'advertiser.example' },
  feed_version: listed.data!.feed_version,
  start_time: 'asap',
  end_time: endTime,
  purchases: [{ product_id: product.product_id, pricing_option_id: pricing.pricing_option_id, budget: 5000 }],
});
const completed = bought.status === 'submitted' ? await bought.submitted!.waitForCompletion() : bought;
if (!completed.success || completed.status !== 'completed') throw new Error(completed.error ?? completed.status);
```

A submitted mutation must settle before it can be controlled; retain the task handle or configure `push_notification_config`. The buyer quick start shows completion, revision-aware control, readback, and correction paths.

## Canonical Reference Resolver

`format_schema` and `platform_extensions` references use immutable `{ uri, digest }` pointers. Use `createCanonicalReferenceResolver` from `@adcp/sdk/canonical-references` instead of raw fetches; it applies SSRF-safe DNS-pinned fetches, redirect blocking, timeout/body caps, SHA-256 verification, structured non-throwing statuses, and bounded policy-scoped LRU caching. The zero-argument cache holds at most 64 entries / 32 MiB estimated retained data; use `createCanonicalReferenceCache({ maxEntries, maxBytes })` to tune the per-resolver budget or inject a fully caller-owned cache.

```typescript
import { createCanonicalReferenceResolver } from '@adcp/sdk/canonical-references';

const resolver = createCanonicalReferenceResolver();
const formatSchemaRef = {
  uri: 'https://publisher.example-ad.com/schemas/slot.json',
  digest: 'sha256:<64 lowercase hex chars>',
};
const result = await resolver.resolveFormatSchema(formatSchemaRef, {
  externalRefDigests: {
    'https://publisher.example-ad.com/shared-slot.json': 'sha256:<64 lowercase hex chars>',
  },
});

if (!result.ok) {
  if (result.error.code === 'digest_mismatch') throw new Error('Reference substitution detected');
  if (result.error.retryable) /* retry later */;
}
```

For `format_schema`, the resolver requires an explicit `$schema`, validates Draft-07 / Draft 2020-12 JSON Schema, inlines only pinned safe `$ref` targets, rejects known catastrophic regex patterns with `error.code: 'budget_exceeded'`, and returns `schemaMeta` on success. Failure statuses are coarse (`unresolvable`, `invalid_document`, `invalid_schema`, `digest_mismatch`, `blocked_unsafe_url`, `invalid_ref`); branch on `error.code` for precise handling. See `docs/guides/CANONICAL-REFERENCE-RESOLVER.md`.

## Transport auth

AdCP is auth-scheme-agnostic at the transport layer. The protocol carries JSON-RPC over HTTP; how the outer envelope is gated is an operator-private deployment choice — bearer tokens, OAuth, mTLS, AWS SigV4 at the edge, an IP allow-list, or RFC 7617 HTTP Basic when the agent sits behind an API gateway with a BasicAuthentication policy (Apigee, Kong, AWS API Gateway, nginx `auth_basic`) are all valid. `get_adcp_capabilities` does NOT advertise the accepted auth schemes; encoding every gateway permutation in the capability payload would couple the protocol to infrastructure choices that change between deployments.

Auth-scheme discovery, when needed, flows through `WWW-Authenticate` (RFC 9110 §11.6.1) and Protected Resource Metadata (RFC 9728) — both consumed by the SDK's auth-diagnostics path. Basic-fronted agents emit `WWW-Authenticate: Basic realm="…"` on a 401; consumers (SDK callers, the CLI's 401-bounce path, LLM agents) should branch on the challenge scheme rather than retrying Bearer indefinitely.

The TypeScript SDK speaks both schemes today. Programmatically: `createTestClient({ auth: { type: 'basic', username, password } })` (RFC 7617) and `createTestClient({ auth: { type: 'bearer', token } })`. From the CLI: `--auth-scheme basic` opts into Basic and `--auth <user:pass>` carries the credential; the default `bearer` remains unchanged.

## 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 eight 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}`,
  'auth-required': r => `Needs authorization: ${r.metadata.taskId}`,
  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. Reusing a key with a different canonical payload returns `IdempotencyConflictError`. Treat that as a reconciliation stop: look up the prior operation by your natural key before deciding whether the new payload is a genuinely new intent. This is also safe across SDK upgrades that strengthen replay identity after the original operation may already have succeeded.

**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) {
  // Reconcile the prior operation by natural key before deciding whether
  // this payload is a genuinely new intent. Do not blindly rotate keys.
  // 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). |
| `ext.adcp.creative_wire` | SDK storyboard/conformance tooling | Transitional 3.1 hint (value: `legacy` or `canonical`) for read requests whose creative dialect is otherwise structurally ambiguous. Application buyer agents do not emit this key; normal SDK methods negotiate from capabilities and payload shape. |

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.<methodName>(params)`. Returns `TaskResult<T>` with `status`, `data`, `error`, `adcpError`, `correlationId`, `deferred`, or `submitted`.

### Protocol

#### `get_adcp_capabilities`

Request parameters for cross-protocol capability discovery.

**Request:**
- Optional: `protocols: ('media_buy' | 'signals' | 'governance' | 'sponsored_intelligence' | 'creative')[]`, `context: Context`

**Response (success branch):**
- Required: `adcp: object`, `supported_protocols: ('media_buy' | 'signals' | 'governance' | 'sponsored_intelligence' | 'creative' | 'brand' | 'measurement')[]`
- Optional: `account: object`, `media_buy: object`, `signals: object`, `governance: object`, `sponsored_intelligence: object`, `brand: object`, `creative: object`, `oauth: object`, +14 more

#### `get_task_status`

Request parameters for get_task_status, the 3.

**Request:**
- Required: `task_id: string`
- Optional: `account: Account Ref`, `include_history: boolean`, `include_result: boolean`, `context: Context`

**Response (success branch):**
- Required: `task_id: string`, `task_type: Task Type`, `protocol: Adcp Protocol`, `status: Task Status`, `created_at: string`, `updated_at: string`
- Optional: `completed_at: string`, `has_webhook: boolean`, `progress: object`, `error: object`, `history: object[]`, `result: object`, `context: Context`

#### `list_tasks`

Request parameters for list_tasks, the 3.

**Request:**
- Optional: `account: Account Ref`, `filters: object`, `sort: object`, `pagination: Pagination Request`, `include_history: boolean`, `context: Context`

**Response (success branch):**
- Required: `query_summary: object`, `tasks: object[]`, `pagination: Pagination Response`
- Optional: `context: Context`

#### `sync_agent_notification_configs`

Register, replace, pause, or clear agent-level webhook subscribers such as capabilities.

**Request:**
- Required: `idempotency_key: string`, `notification_configs: Agent Notification Config[]`
- Optional: `dry_run: boolean`, `context: Context`

**Response (success branch):**
- Required: `action: 'updated' | 'unchanged' | 'cleared' | 'failed'`
- Optional: `dry_run: boolean`, `notification_configs: Agent Notification Config[]`, `errors: Error[]`, `context: Context`

#### `sync_principal`

Declaratively synchronize caller-scoped webhooks and reusable reporting destinations.

**Request:**
- Required: `idempotency_key: string`, `configuration: object`
- Optional: `expected_configuration_version: string`, `expected_principal_kind: Principal Kind`, `dry_run: boolean`, `context: Context`

**Response (success branch):**
- Required: `result: Applied principal configuration | Validated principal dry run | Failed principal sync`
- Optional: `context: Context`

#### `get_principal`

Read caller-scoped connection configuration, version, and destination setup states without mutation.

**Request:**
- Optional: `context: Context`

**Response (success branch):**
- Required: `result: union`
- Optional: `context: Context`

### Account Management

#### `list_account_changes`

Request parameters for reading the durable account change feed.

**Request:**
- Required: `account: Account Ref`
- Optional: `adcp_version: string`, `cursor: string`, `starting_position: 'earliest' | 'latest'`, `resource_types: string[]`, `max_results: integer`, `context: Context`

**Response (success branch):**
- Required: `changes: Account Change[]`, `cursor: string`, `has_more: boolean`, `available_since: string`, `generated_at: string`, `status: 'completed'`
- Optional: `source_coverage: object[]`, `errors: Error[]`, `context: Context`

#### `list_accounts`

Request parameters for listing accounts accessible to the authenticated agent.

**Request:**
- Optional: `account: Account Ref`, `status: 'active' | 'pending_approval' | 'rejected' | 'payment_required' | 'suspended' | 'closed'`, `pagination: Pagination Request`, `sandbox: boolean`, `include_webhook_activity: boolean`, `webhook_activity_limit: integer`, `context: Context`

**Response (success branch):**
- Required: `accounts: Account With Authorization[]`
- Optional: `errors: Error[]`, `pagination: Pagination Response`, `context: Context`

#### `sync_accounts`

Request parameters for syncing advertiser accounts with a seller.

**Request:**
- Required: `idempotency_key: string`, `accounts: (ProvisioningMode | SettingsUpdateMode)[]`
- 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: Error[]`, `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`

AdCP 3.

**Request:**
- Required: `buying_mode: 'brief' | 'wholesale' | 'refine'`
- Optional: `idempotency_key: string`, `brief: string`, `refine: object[]`, `brand: Brand Ref`, `acceptance_context: Acceptance Context`, `catalog: Catalog`, `account: Account Ref`, `preferred_delivery_types: Delivery Type[]`, +14 more

**Response (success branch):**
- Optional: `products: Product[]`, `targeting_resolution: Get Products Targeting Resolution`, `extensions: object`, `proposals: Proposal[]`, `errors: Error[]`, `reason: string`, `suggestions: string[]`, `property_list_applied: boolean`, +11 more

**Watch out:**
- `cache_scope` is required whenever the response includes `products` or `unchanged: true`. Use `public` for the universal rate card and `account` for account-specific rate cards or pricing overlays.
- SDK server handlers may omit `cache_scope` only for no-account product feeds; the framework can safely infer `public` only when there is no inline account and no auth-derived/resolved account.

#### `list_products`

Request parameters for synchronous product-offer reads.

**Request:**
- Optional: `adcp_version: string`, `idempotency_key: string`, `context_id: string`, `context: Context`, `governance_context: string`, `push_notification_config: Push Notification Config`, `account: Canonical Account Ref`, `brand: Brand Key`, +6 more

**Response (success branch):**
- Required: `outcome: 'listed'`, `products: Canonical Product[]`, `feed_version: string`, `cache_scope: 'public' | 'account'`
- Optional: `next_cursor: string`, `pricing_version: string`, `incomplete: object[]`, `replayed: 'true'`, `context: Context`

#### `request_proposals`

Request parameters for creating actionable seller proposals.

**Request:**
- Required: `idempotency_key: string`, `brief: string`
- Optional: `adcp_version: string`, `context_id: string`, `context: Context`, `governance_context: string`, `push_notification_config: Push Notification Config`, `account: Canonical Account Ref`, `brand: Brand Key`, `criteria: Product Discovery Criteria`, +1 more

**Response (success branch):**
- Required: `outcome: 'proposed'`, `proposals: Canonical Proposal[]`, `products: Canonical Product[]`
- Optional: `adcp_version: string`, `incomplete: object[]`, `targeting_resolution: Get Products Targeting Resolution`, `status: 'completed'`, `message: string`, `errors: Error[]`, `context: Context`, `replayed: 'true'`

#### `refine_proposals`

Request parameters for creating one or more proposal revisions.

**Request:**
- Required: `idempotency_key: string`, `refinements: Proposal Refinement[]`
- Optional: `adcp_version: string`, `context_id: string`, `context: Context`, `governance_context: string`, `push_notification_config: Push Notification Config`

**Response (success branch):**
- Required: `results: union[]`, `products: Canonical Product[]`
- Optional: `adcp_version: string`, `status: 'completed'`, `message: string`, `errors: Error[]`, `context: Context`, `replayed: 'true'`

#### `decline_proposals`

Request parameters for terminally declining one or more proposals.

**Request:**
- Required: `idempotency_key: string`, `declines: Proposal Decline[]`
- Optional: `adcp_version: string`, `context_id: string`, `context: Context`, `governance_context: string`, `push_notification_config: Push Notification Config`, `opportunity: Opportunity Context`

**Response (success branch):**
- Required: `results: object[]`
- Optional: `message: string`, `errors: Error[]`, `context: Context`, `replayed: 'true'`

#### `buy_products`

Create a MediaBuy directly from canonical published product offers.

**Request:**
- Required: `idempotency_key: string`, `account: Canonical Account Ref`, `feed_version: string`, `purchases: Product Purchase Input[]`, `start_time: Start Timing`, `end_time: string`
- Optional: `adcp_version: string`, `brand: Brand Key`, `advertiser_industry: Advertiser Industry`, `pricing_version: string`, `total_budget: object`, `daily_budget_cap: number`, `frequency_cap: Media Buy Frequency Cap`, `budget_cap_timezone: string`, +12 more

#### `accept_proposal`

Accept a committed new-buy, amendment, or cancellation proposal.

**Request:**
- Required: `idempotency_key: string`, `account: Canonical Account Ref`, `proposal_id: string`, `proposal_terms_digest: string`
- Optional: `adcp_version: string`, `total_budget: object`, `daily_budget_cap: number`, `budget_cap_timezone: string`, `io_acceptance: object`, `purchase_order_ref: string`, `governance_context: string`, `push_notification_config: Push Notification Config`, +3 more

#### `control_media_buy`

Apply operational controls inside accepted commercial terms.

**Request:**
- Required: `idempotency_key: string`, `account: Canonical Account Ref`, `media_buy_id: string`, `revision: integer`
- Optional: `adcp_version: string`, `name: string`, `paused: boolean`, `canceled: 'true'`, `cancellation_reason: string`, `total_budget: object`, `daily_budget_cap: number,null`, `frequency_cap: Media Buy Frequency Cap | null`, +9 more

**Response (success branch):**
- Required: `status: 'completed'`, `media_buy_id: string`, `revision: integer`
- Optional: `media_buy_status: Media Buy Status`, `implementation_date: string,null`, `affected_package_ids: string[]`, `available_actions: Canonical Media Buy Action[]`, `warnings: Warning[]`, `context: Context`, `replayed: 'true'`

#### `list_creative_formats`

Deprecated 3.

**Request:**
- Optional: `format_ids: Format Id[]`, `asset_types: Asset Content Type[]`, `max_width: integer`, `max_height: integer`, `min_width: integer`, `min_height: integer`, `is_responsive: boolean`, `name_search: string`, +9 more

**Response (success branch):**
- Required: `formats: Format[]`
- Optional: `source: 'publisher' | 'aao_mirror' | 'agent_derived'`, `creative_agents: object[]`, `errors: Error[]`, `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`

AdCP 3.

**Request:**
- Required: `idempotency_key: string`, `account: Account Ref`, `brand: Brand Ref`, `start_time: Start Timing`, `end_time: string`
- Optional: `governance_context: string`, `plan_id: string`, `proposal_id: string`, `opportunity: Opportunity Context`, `total_budget: object`, `daily_budget_cap: number`, `frequency_cap: Media Buy Frequency Cap`, `budget_cap_timezone: string`, +15 more

**Response (success branch):**
- Required: `media_buy_id: string`, `confirmed_at: string,null`, `revision: integer`, `packages: Package[]`
- Optional: `proposal_id: string`, `name: string`, `account: Account`, `invoice_recipient: Business Entity`, `media_buy_status: Media Buy Status`, `creative_deadline: string`, `currency: string`, `total_budget: number`, +12 more

**Watch out:**
- Server handlers should return business lifecycle state as `media_buy_status`. The framework owns the task envelope `status`; do not return top-level `status` as the media-buy state.

#### `update_media_buy`

AdCP 3.

**Request:**
- Required: `account: Account Ref`, `media_buy_id: string`, `idempotency_key: string`
- Optional: `governance_context: string`, `name: string`, `revision: integer`, `paused: boolean`, `canceled: 'true'`, `cancellation_reason: string`, `start_time: Start Timing`, `end_time: string`, +13 more

**Response (success branch):**
- Required: `media_buy_id: string`, `revision: integer`
- Optional: `name: string`, `media_buy_status: Media Buy Status`, `currency: string`, `total_budget: number`, `daily_budget_cap: number`, `frequency_cap: Media Buy Frequency Cap`, `budget_cap_timezone: string`, `budget_allocation: Budget Allocation`, +10 more

**Watch out:**
- Server handlers should return business lifecycle state as `media_buy_status`. The framework owns the task envelope `status`; do not return top-level `status` as the media-buy state.

#### `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 | Media Buy Status[]`, `indicator_types: Indicator Type[]`, `include_snapshot: boolean`, `include_history: integer`, `include_webhook_activity: boolean`, `webhook_activity_limit: integer`, +2 more

**Response (success branch):**
- Required: `media_buys: Indicator Bearing[]`
- Optional: `errors: Error[]`, `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[]`, `reporting_revision_id: string`, `pagination: Pagination Request`, `status_filter: Media Buy Status | Media Buy Status[]`, `start_date: string`, `end_date: string`, `include_package_daily_breakdown: boolean`, +6 more

**Response (success branch):**
- Required: `reporting_period: object`, `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`, `reporting_revision_binding: object`, `reporting_revision: Reporting Revision`, `reporting_rows: object[]`, +7 more

#### `get_reporting_status`

Request parameters for reconciling managed reporting obligations, revisions, and materializations.

**Request:**
- Required: `account: Canonical Account Ref`, `view: 'summary' | 'periods' | 'revision'`
- Optional: `media_buy_ids: string[]`, `delivery_config_ids: string[]`, `feed_purposes: ('pacing' | 'analytics' | 'billing')[]`, `period: object`, `health: Reporting Health[]`, `finality: Reporting Finality[]`, `reporting_revision_id: string`, `changes_after: string`, +2 more

**Response (success branch):**
- Required: `status: 'completed'`
- Optional: `view: 'summary' | 'periods' | 'revision'`, `ledger_snapshot_id: string`, `ledger_as_of: string`, `changes_checkpoint: string`, `account_id: string`, `scope: object`, `health: Reporting Health`, `coverage: Reporting Coverage`, +15 more

#### `sync_reporting_status`

Submit authenticated consumer status for expected reporting periods.

**Request:**
- Required: `account: Canonical Account Ref`, `idempotency_key: string`, `statuses: Reporting Consumer Status[]`
- Optional: `adcp_version: string`, `context: Context`

**Response (success branch):**
- Required: `status: 'completed'`, `results: (Recorded reporting consumer status | Unchanged reporting consumer status | Failed reporting consumer status)[]`
- Optional: `context: Context`

**Watch out:**
- A `completed` envelope does not mean every item succeeded: inspect each per-item `result`. For a schema-valid envelope, results map one-for-one to submitted statuses in request order.
- `recorded_at` is seller-authored and response-only. Never send it in `statuses[]`.

#### `sync_reporting_receipts`

Submit authenticated consumer reconciliation receipts for durable reporting materializations.

**Request:**
- Required: `account: Canonical Account Ref`, `idempotency_key: string`
- Optional: `adcp_version: string`, `receipts: Reporting Receipt[]`, `adjustment_receipts: Reporting Adjustment Receipt[]`, `context: Context`

**Response (success branch):**
- Required: `status: 'completed'`, `results: union[]`
- Optional: `context: Context`

#### `provide_performance_feedback`

Request parameters for sharing performance outcomes with publishers.

**Request:**
- Required: `idempotency_key: string`
- Optional: `context: Context`

**Response (success branch):**
- Required: `success: 'true'`
- Optional: `feedback_id: string`, `application_status: 'accepted' | 'applied' | 'not_applied'`, `status_reason: string`, `received_at: string`, `applied_at: string`, `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: Event[]`, `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: Catalog[]`, `item_availability_updates: Catalog Item Availability Update[]`, `item_availability_queries: Catalog Item Availability Ref[]`, `catalog_ids: string[]`, `delete_missing: boolean`, `dry_run: boolean`, `validation_mode: Validation Mode`, `push_notification_config: Push Notification Config`, +1 more

**Response (success branch):**
- Required: `catalogs: object[]`
- Optional: `status: 'completed'`, `dry_run: boolean`, `item_availability_updates: Catalog Item Availability Update Result[]`, `item_availability_states: Catalog Item Availability State[]`, `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: `governance_context: string`, `message: string`, `creative_manifest: Creative Manifest`, `creative_representation_set: Creative Representation Set`, `representation_destination: Representation Destination`, `representation_selection_strategy: Representation Selection Strategy`, `creative_id: string`, `concept_id: string`, +29 more

**Response (success branch):**
- Required: `creative_manifest: Creative Manifest`
- Optional: `build_variant_id: string`, `recipe_hash: string`, `sandbox: boolean`, `expires_at: string`, `preview: object`, `preview_error: Error`, `pricing_option_id: string`, `vendor_cost: number`, +3 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`, `target_capability_id: string`, `format_id: Format Id`, `inputs: object[]`, `template_id: string`, `quality: Creative Quality`, `output_format: Preview Output Format`, `item_limit: integer`, +6 more

**Response (success branch):**
- Required: `response_type: 'single'`, `previews: object[]`
- Optional: `quality_used: Creative Quality`, `interactive_url: string`, `expires_at: 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`

Deprecated 3.

**Request:**
- Optional: `format_ids: Format Id[]`, `type: 'audio' | 'video' | 'display' | 'dooh'`, `asset_types: Asset Content Type[]`, `max_width: integer`, `max_height: integer`, `min_width: integer`, `min_height: integer`, `is_responsive: boolean`, +10 more

**Response (success branch):**
- Required: `formats: Format[]`
- Optional: `creative_agents: object[]`, `errors: Error[]`, `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`).

#### `list_transformers`

Request parameters for discovering account-scoped creative transformers (the creative analog of products), with optional brief filtering, per-param option expansion, and pricing.

**Request:**
- Optional: `transformer_ids: string[]`, `input_format_ids: Format Id[]`, `output_format_ids: Format Id[]`, `input_format_kinds: Canonical Format Kind[]`, `output_capability_ids: string[]`, `name_search: string`, `brief: string`, `expand_params: string[]`, +5 more

**Response (success branch):**
- Required: `transformers: Transformer[]`
- Optional: `errors: Error[]`, `pagination: Pagination Response`, `context: Context`

#### `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: Error[]`, `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`, `assignment_projection: 'all' | 'matching'`, `assignment_limit: integer`, `include_snapshot: boolean`, `include_items: boolean`, +8 more

**Response (success branch):**
- Required: `query_summary: object`, `pagination: Pagination Response`, `creatives: (Listed creative (named-format reference) | Listed creative (canonical format kind))[]`
- Optional: `format_summary: object`, `status_summary: object`, `errors: Error[]`, `sandbox: boolean`, `context: Context`

#### `sync_creatives`

Request parameters for syncing creative assets with upsert semantics.

**Request:**
- Required: `account: Account Ref`, `idempotency_key: string`
- Optional: `creatives: Creative Asset[]`, `creative_ids: string[]`, `assignments: object[]`, `assignment_operations: (Assign or update | Unassign | Replace assignment)[]`, `delete_missing: boolean`, `dry_run: boolean`, `validation_mode: Validation Mode`, `push_notification_config: Push Notification Config`, +1 more

**Response (success branch):**
- Required: `creatives: object[]`
- Optional: `dry_run: boolean`, `sandbox: boolean`, `context: Context`

#### `validate_input`

Request parameters for validating a creative manifest against canonical formats and/or specific products without committing to a render.

**Request:**
- Required: `manifest: Creative Manifest`
- Optional: `account: Account Ref`, `brand: Brand Ref`, `targets: union[]`

**Response (success branch):**
- Required: `results: Validate Input Result[]`

**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: `discovery_mode: 'brief' | 'wholesale'`, `account: Account Ref`, `signal_spec: string`, `signal_refs: Signal Ref[]`, `signal_ids: Signal Id[]`, `destinations: Destination[]`, `countries: string[]`, `filters: Signal Filters`, +7 more

**Response (success branch):**
- Optional: `signals: Signal Listing[]`, `errors: Error[]`, `incomplete: object[]`, `wholesale_feed_version: string`, `pricing_version: string`, `cache_scope: 'public' | 'account'`, `unchanged: 'true'`, `pagination: Pagination Response`, +2 more

#### `activate_signal`

Request parameters for activating a signal on a specific platform/account.

**Request:**
- Required: `signal_agent_segment_id: string`, `destinations: Destination[]`, `idempotency_key: string`
- Optional: `action: 'activate' | 'deactivate'`, `pricing_option_id: string`, `governance_context: string`, `account: Account Ref`, `context: Context`

**Response (success branch):**
- Required: `deployments: Deployment[]`
- 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: Base Property Source[]`, `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: Base Property Source[]`, `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: Identifier[]`, `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: Property List[]`
- 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: Base Collection Source[]`, `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: Base Collection Source[]`, `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: Collection List[]`
- 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: Channels[]`, `languages: string[]`, `countries: string[]`, `pagination: Pagination Request`, `context: Context`

**Response (success branch):**
- Required: `standards: Content Standards[]`
- 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: Policy Entry[]`, `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: Policy Entry[]`, `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: Creative Feature Result[]`
- Optional: `detail_url: string`, `audit_observations: Audit Observation[]`, `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`
- Optional: `check_id: string`, `purchase_type: Purchase Type`, `seller_response: object`, `delivery: object`, `error: Reported Outcome Error`, `governance_context: string`, `context: Context`

**Response (success branch):**
- Required: `outcome_id: string`, `outcome_state: 'accepted' | 'findings'`
- Optional: `committed_budget: number`, `delivery_reconciliation_status: 'consistent' | 'measurement_variance' | 'disputed' | 'unmatched' | 'closed_unresolved'`, `delivery_period_state: 'open' | 'closed'`, `findings: object[]`, `plan_summary: object`, `replayed: boolean`, `context: Context`

#### `report_plan_adjustment`

Seller-authenticated append-only commitment adjustment report.

**Request:**
- Required: `action: 'report' | 'review'`, `plan_id: string`, `idempotency_key: string`
- Optional: `outcome_id: string`, `adjustment_id: string`, `decision: 'accept' | 'dispute'`, `seller_reference: string`, `seller_adjustment_id: string`, `adjustment_type: 'decommitment' | 'refund' | 'credit' | 'makegood'`, `amount: object`, `reason: string`, +3 more

**Response (success branch):**
- Required: `adjustment_id: string`, `adjustment_state: 'reported' | 'verified' | 'disputed'`, `adjustment_type: 'decommitment' | 'refund' | 'credit' | 'makegood'`, `amount: object`, `headroom_restored: number`, `plan_summary: object`
- Optional: `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: Purchase Type[]`, `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: `caller: string`
- Optional: `plan_id: string`, `purchase_type: Purchase Type`, `target_agent: string`, `proposed_commitment: object`, `execution_commitment: object`, `tool: string`, `payload: object`, `proposal: Canonical Proposal`, +9 more

**Response (success branch):**
- Required: `check_id: string`, `verdict: Governance Decision`, `explanation: string`
- Optional: `check_type: 'intent' | 'execution'`, `plan_id: string`, `findings: object[]`, `conditions: object[]`, `consultation_context: string`, `expires_at: string`, `next_check: string`, `delivery_statement: object`, +7 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[]`, `sponsored_context: Si Sponsored Context`, `total_matching: integer`, `unavailable_reason: string`, +3 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`, `sponsored_context_receipt: Si Sponsored Context Receipt`

**Response (success branch):**
- Required: `session_id: string`, `session_status: Si Session Status`
- Optional: `response: object`, `negotiated_capabilities: Si Capabilities`, `sponsored_context: Si Sponsored Context`, `session_ttl_seconds: integer`, `errors: Error[]`, `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`, `sponsored_context_receipt: Si Sponsored Context Receipt`, `context: Context`

**Response (success branch):**
- Required: `session_id: string`, `session_status: Si Session Status`
- Optional: `response: object`, `mcp_resource_uri: string`, `sponsored_context: Si Sponsored Context`, `handoff: object`, `errors: Error[]`, `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: Error[]`, `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.

**AdCP 3.1.10 TMPX boundary:**
- Public `identity_match` calls return `IdentityMatchResponseRouterPublisher`: provider chunks are attributed under `tmpx_providers[provider_id].chunks`.
- Router implementations validate upstream identity providers with `IdentityMatchResponseProviderRouter`, whose root field is `tmpx_chunks`.
- Providers register local `tmpx_slots`; publisher-owned `PublisherTMPXMacroMapping` resolves each `(provider_id, slot_id)` to a local destination. Provider responses never carry publisher macro names.
- Both response hops forbid `context`/`ext` and opposite-hop TMPX fields. Chunk arrays contain one or two strict `{ slot_id, value }` entries.

## 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 → search_brands`

**Distributed brand.json mutual assertion resolves identity and relationship trust** — Consumer-under-test storyboard for AdCP 3.1 distributed brand.json: a house portfolio points at a child Brand Canonical Document, the child points back with house_domain, mutual assertion unlocks relationship trust, one-sided claims do not, identity stays brand-authored, compliance merges strictest-of, managed_by is directory metadata, and typed trademarks validate at the static-file layer.
Flow: `comply_test_controller`

**Signed brand responses are fresh and bound to request, task, and tenant** — Consumer-under-test matrix for verify_brand_claim and verify_brand_claims payload-envelope verification: valid responses pass; expired, replayed/request-mismatched, and cross-tenant responses fail closed.
Flow: `comply_test_controller → verify_brand_claim → comply_test_controller → verify_brand_claims`

**Partners MUST NOT extend trust on a single signed verify_brand_claim response** — Red conformance test for the asymmetric trust model on verify_brand_claim. A partner that auto-provisions, propagates governance, or otherwise extends relationship trust on the strength of one signed `owned` response fails — assertion direction requires reciprocation.
Flow: `comply_test_controller → verify_brand_claim → comply_test_controller → verify_brand_claim → comply_test_controller → verify_brand_claim`

**Brand agent accepts a signed rights authorization** — Verifies that a valid task- and payload-bound governance token permits a paid rights grant and that the grant persists.
Flow: `acquire_rights → sync_accounts → sync_governance → get_rights → sync_plans → check_governance → acquire_rights → update_rights`

**Brand agent rejects rights acquisition without governance approval** — Verifies that a rights agent claiming acquire_rights governance enforcement fails closed without signed authorization and creates no grant.
Flow: `acquire_rights → sync_accounts → sync_governance → get_rights → acquire_rights → update_rights`

**Update a seeded rights grant and reject unknown references** — Verifies that update_rights atomically updates an acquired grant and rejects an unknown rights_id with REFERENCE_NOT_FOUND.
Flow: `comply_test_controller → update_rights`

### Creative

**Creative lifecycle** — Baseline creative lifecycle on a stateful platform: sync display creatives, list with filtering, and preview renderings.
Flow: `get_adcp_capabilities → sync_creatives → list_creatives → get_creative_delivery → preview_creative`

**Creative library asset-type filtering** — Filters list_creatives by published_post and zip asset types, including OR semantics, mixed assets, empty results, and composition with format_ids.
Flow: `list_creatives`

**Creative report_usage rejects out-of-band billing** — Verifies that a creative agent declaring bills_through_adcp: false rejects report_usage records with accepted: 0 and BILLING_OUT_OF_BAND.
Flow: `get_adcp_capabilities → report_usage`

**Creative canonical supported formats** — Verifies the 3.2 creative-agent canonical path: targetable supported_formats capability IDs, build_creative routing, and unsupported target rejection.
Flow: `get_adcp_capabilities → build_creative`

**Creative lifecycle webhooks** — Registers account-level creative lifecycle notifications, forces a seller-side status transition, observes creative.status_changed, and verifies list_creatives snapshot repair plus optional creative.purged coverage.
Flow: `sync_accounts → sync_creatives → comply_test_controller → expect_webhook → list_creatives → comply_test_controller → expect_webhook → comply_test_controller → expect_webhook → list_creatives → comply_test_controller → expect_webhook → list_creatives`

**Creative revision identity is immutable and reads back exactly** — Verifies capability-gated revision echo, immutable content reuse, non-mutation on mismatch, and current library readback.
Flow: `get_adcp_capabilities → sync_creatives → list_creatives`

**Creative evaluator authentication boundary** — Verifies that build_creative evaluator pointers use the verifier allowlist, keep credentials out of payloads, and invoke accepted evaluator agents through get_creative_features.
Flow: `get_adcp_capabilities → build_creative → comply_test_controller → build_creative`

**Sales agent applies deterministic creative filters** — Verifies a sales agent applies creative-status and media-buy-assignment filters conjunctively.
Flow: `list_creatives`

**Native in-feed creative lifecycle** — End-to-end native_in_feed conformance: format discovery, full 12-slot asset bundle submission with pixel trackers, per-constraint validation rejection paths, and feed-rendered preview.
Flow: `get_adcp_capabilities → sync_creatives → preview_creative`

**Creative materialized localization** — Verifies capability discovery, materialized localized sync, explicit default/unmatched behavior, and exact list readback.
Flow: `get_adcp_capabilities → sync_creatives → list_creatives`

**Policy-backed creative rejections** — Reports each violated registry policy as its own CREATIVE_REJECTED error entry.
Flow: `get_products → sync_creatives`

**Sales agent with creative capabilities** — Stateful sales agent that accepts pushed creative assets and renders them in its environment.
Flow: `get_adcp_capabilities → sync_creatives → preview_creative`

**Seller accepts a provenance carve-out claim surfaced as an audit observation** — Buyer submits AI-assisted creatives with human_oversight directed/edited and disclosure.required false. Seller invokes an on-list verifier, records OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED audit observations, and accepts the creatives rather than treating the observations as rejections.
Flow: `get_products → sync_creatives → comply_test_controller`

**Seller enforces provenance_requirements on sync_creatives** — Seller publishes provenance_requirements + accepted_verifiers on a product. Four structural rejections (no provenance, missing digital_source_type, off-list verifier, missing disclosure), then a corrected resubmission with on-list verifier is accepted.
Flow: `get_products → sync_creatives`

**Seller refutes a buyer's provenance claim via on-list verifier** — Buyer attaches a digital_source_type claim. Seller invokes get_creative_features against an on-list verifier (creative_policy.accepted_verifiers); when the verifier contradicts the claim, seller rejects with PROVENANCE_CLAIM_CONTRADICTED carrying audit-safe error.details.
Flow: `get_products → sync_creatives`

**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 → 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 → preview_creative → build_creative`

**Creative agent accepts a signed paid-build authorization** — Verifies that a valid task- and payload-bound governance token permits a paid creative build and returns a retained artifact.
Flow: `build_creative → sync_accounts → sync_governance → list_transformers → sync_plans → check_governance → build_creative`

**Creative agent rejects paid execution without governance approval** — Verifies that a creative agent claiming build_creative governance enforcement rejects an unauthorized paid render before vendor execution.
Flow: `build_creative → sync_accounts → sync_governance → list_transformers → build_creative`

**Canonical format validate_input** — Validates 3.1 canonical-format dry-run semantics: structural pass/fail across canonical slots and unvalidatable_nondeterministic for seeded products.
Flow: `validate_input`

**CTV experience profile validate_input** — Validates AdCP 3.2 ctv_ad_experience matrix pairings, duration/interactivity constraint profiles, the menu focus/video pairing, and non-blocking activation-copy warnings via validate_input against seeded CTV products.
Flow: `validate_input`

**Premium display canonical validation** — Validates AdCP 3.2 seller_rendered_stateful_display supply-mode contracts (components, rendered_canvases, layered_source), single-state reveal shape, policy floors, and coordinated_placements sequence/serving_policy, alongside transition/canvas coverage and shared-slot resolution.
Flow: `validate_input`

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

**Governance preserves bounded failed-outcome audit evidence** — Verifies that a buyer-reported failed seller interaction is accepted without spend mutation and is emitted verbatim as untrusted audit evidence.
Flow: `sync_plans → check_governance → 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 → report_plan_outcome → get_media_buy_delivery → check_governance → report_plan_adjustment → report_plan_outcome → report_plan_adjustment → report_plan_outcome → report_plan_adjustment → check_governance → report_plan_outcome → report_plan_adjustment`

**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`

**Cross-role governance conformance index** — Non-executable index connecting the universal governance contract to capability-gated, role-specific proof storyboards.

### Governance

**Property-list change webhook signing** — Property-list change notifications use the RFC 9421 webhook profile and match the published payload contract.
Flow: `create_property_list → update_property_list → expect_webhook → expect_webhook_signature_valid`

**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 → get_creative_features → get_media_buy_artifacts`

**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`

### 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 → get_adcp_capabilities → sync_accounts → sync_governance → get_products → create_media_buy → get_media_buys → get_media_buy_delivery → sync_governance → get_products → create_media_buy → get_media_buys → get_media_buy_delivery`

**Seller exposes structured acceptance-policy discovery** — Verifies that an advertised acceptance-policy catalog is digest-pinned and that product-specific profiles are discoverable.
Flow: `get_adcp_capabilities → get_products`

**Shared-account change feed convergence** — Bootstraps a shared account, forces a connected-platform creative change, observes account.change_recorded, drains list_account_changes, and repairs through list_creatives.
Flow: `sync_accounts → list_account_changes → comply_test_controller → expect_webhook → list_account_changes → list_creatives → comply_test_controller → expect_webhook → list_account_changes → list_creatives → list_account_changes → comply_test_controller → list_account_changes → list_creatives → list_account_changes`

**Buyer-selected account timezone resolution** — Verifies advertised timezone reconciliation, cold-start reconnect, and separate account identities when a buyer selects another timezone.
Flow: `get_adcp_capabilities → sync_accounts → list_accounts → sync_accounts → list_accounts → get_adcp_capabilities → sync_accounts → list_accounts`

**Seller-assigned account timezone discovery** — Verifies that buyers omit timezone input and discover the seller-assigned immutable value from account readback.
Flow: `get_adcp_capabilities → sync_accounts → list_accounts`

**Seller-fixed account timezone resolution** — Verifies that seller-fixed account capabilities declare one timezone and every account write and read echoes it.
Flow: `get_adcp_capabilities → sync_accounts → list_accounts`

**Seller discovers audience activation paths honestly** — Verifies capability and product declarations, vendor-constrained matching, OR semantics, omitted-field wildcards, and exclusion of undeclared products.
Flow: `get_adcp_capabilities → get_products`

**Seller fulfills a media buy with audience targeting from a synced CRM audience** — Verifies that a seller advertising audience_targeting can ingest a CRM audience via sync_audiences, accept a media buy whose targeting_overlay references the bound audience_id, reject malformed audience references, and report delivery for the audience-targeted buy. Sibling to media_buy_seller/performance_buy_flow on the audience side: the unbound-id rejection is the discriminating assertion.
Flow: `sync_accounts → get_products → sync_audiences → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Seller answers flexible-window availability with eligibility-aware status** — Verifies offer_filters.availability_horizon discovery: time-dimensioned forecast points that partition the horizon, availability_status computed from booking eligibility (not only holds), forecast excluded from conditional reads, and PRODUCT_UNAVAILABLE on buys against closed or too-short windows.
Flow: `sync_accounts → list_products → buy_products`

**Seller carries product allowed actions into per-buy available actions** — Validates the AdCP 3.1 media-buy action-discovery flow: product allowed_actions[], buy available_actions[], SLAWindow duration fields, self-serve mutation, and ACTION_NOT_ALLOWED enforcement.
Flow: `get_products → sync_creatives → create_media_buy → get_media_buys → update_media_buy`

**Seller rejects ambiguous fixed cost-control binding** — Verifies that fixed media-buy cost caps bind deterministically and cannot mean different result units across inheriting packages.
Flow: `sync_accounts → create_media_buy`

**Seller preserves bidding scope and explicit automatic overrides** — Verifies that a seller advertising fixed media-buy max_bid support accepts an inherited default, echoes it once, and does not materialize package copies. When package automatic support is also advertised, it verifies an explicit automatic override.
Flow: `sync_accounts → get_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys`

**Account-based budget-cap timezone resolution** — Verifies that aggregate and package daily caps default to Account.timezone and remain stable on readback.
Flow: `get_adcp_capabilities → list_accounts → list_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → get_account_financials`

**Feature-fixed budget-cap timezone resolution** — Verifies that aggregate and package daily caps default to the budget capability's fixed timezone and remain stable on readback.
Flow: `get_adcp_capabilities → list_accounts → list_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → get_account_financials`

**Buyer budget-cap timezone override** — Verifies that an advertised buyer timezone override applies to aggregate and package caps and is echoed on readback.
Flow: `list_accounts → create_media_buy → get_media_buys → create_media_buy → get_media_buys`

**Unadvertised budget-cap timezone override rejection** — Verifies that an aggregate-cap timezone override is rejected when buyer override support is false or absent.
Flow: `create_media_buy`

**Buyer-managed catalog items support immediate availability updates** — Verifies capability-gated, atomic, synchronous suppression and restoration of buyer-managed catalog items.
Flow: `sync_catalogs → comply_test_controller → sync_catalogs → comply_test_controller → sync_catalogs → comply_test_controller → sync_catalogs → comply_test_controller → sync_catalogs`

**Seller projects proposal change rights through MediaBuy state** — Verifies that accepted change terms remain binding while available_actions narrows by status, routes seller-managed work, and links with change_term_id.
Flow: `get_media_buys → control_media_buy → get_media_buys → control_media_buy`

**Seller fulfills a media buy with a clicks optimization goal (target CPC)** — Verifies that a seller advertising clicks in supported_optimization_metrics can accept a media buy whose metric-kind optimization_goal targets cost-per-click and reports clicks + cost_per_click on delivery. Lightweight sibling to the performance scenarios on the click-optimization side: sellers that don't advertise clicks as an optimization metric (rare — most do; pure brand sellers like broadcast TV upper-funnel video are the not_applicable population) grade not_applicable.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Buyer commits collection selection and the seller echoes it as concrete selectors on readback** — Verifies mode: selected accepts an explicit domain-qualified subset, mode: default materializes the product's full bundle on readback, package readback always echoes committed collection_selection as concrete selectors, and a fixed (non-selectable) bundle rejects partial selection while accepting an exact restatement.
Flow: `get_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → create_media_buy`

**Seller completes the compact direct-buy lifecycle** — Verifies published-offer discovery, direct purchase, revision-checked operational control, and authoritative readback through AdCP 3.2 compact tools.
Flow: `comply_test_controller → list_products → buy_products → control_media_buy → get_media_buys`

**Seller completes the compact product lifecycle** — Verifies product discovery, proposal creation, finalization, acceptance, operational control, and readback through the AdCP 3.2 compact lifecycle.
Flow: `comply_test_controller → list_products → request_proposals → refine_proposals → accept_proposal → control_media_buy → get_media_buys`

**Seller fulfills a video media buy with a completed_views optimization goal (target CPCV)** — Verifies that a seller advertising completed_views in supported_optimization_metrics can accept a media buy whose metric-kind optimization_goal targets completed views at a buyer-supplied view_duration_seconds, reject view_duration_seconds values not in the product's metric_optimization.supported_view_durations, and report completed_views + completion_rate on delivery. Sibling to reach_buy_flow on the video-completion side: sellers without video inventory (display-only DSPs, retail-media networks, audio-only sellers without video products) grade not_applicable.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → 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`

**Seller exposes create_media_buy tasks through read and list lifecycle tools** — Forces create_media_buy into the submitted arm, reconciles the task through get_task_status and list_tasks, completes it deterministically, and verifies the terminal media-buy result.
Flow: `comply_test_controller → create_media_buy → get_task_status → list_tasks → comply_test_controller → get_task_status → list_tasks`

**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 preserves synced-audience and seller-signal targeting provenance** — Verifies that a seller keeps buyer-managed sync_audiences resources on audience_include/audience_exclude, keeps get_signals results on signal_targeting_groups, rejects cross-path substitutions, and preserves exact signal identity and pricing on readback.
Flow: `get_products → get_signals → create_media_buy → get_media_buys → sync_audiences → create_media_buy`

**Seller rejects execution of a declined committed proposal** — Verifies decline terminality at both canonical and compatibility proposal-execution boundaries.
Flow: `comply_test_controller → request_proposals → refine_proposals → decline_proposals → accept_proposal → create_media_buy`

**Seller rejects refinement of a declined proposal** — Verifies that decline_proposals is terminal for later refine_proposals attempts.
Flow: `comply_test_controller → request_proposals → decline_proposals → refine_proposals`

**Seller compiles portable age intent exactly and exposes lossless readback** — Verifies product-scoped continuous, interval, and signal-backed demographic execution; explicit open-bound and unknown-age handling; exact bucket unions; direct-create rejection of inexact predicates; and exact package readback.
Flow: `get_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → create_media_buy → get_media_buys`

**Dependency impairment end-to-end — resource transition propagates to media buy health** — Forces a creative from approved → rejected on a non-terminal media buy, verifies the buy reflects health: impaired with a matching impairments[] entry, and recovers via assignment swap to a different creative (canonical recovery vector — re-approval of the same creative_id is uncommon in production).
Flow: `get_products → create_media_buy → sync_creatives → update_media_buy → comply_test_controller → get_media_buys → comply_test_controller → list_creatives → get_media_buys → sync_creatives → comply_test_controller → update_media_buy → get_media_buys`

**Audience dependency impairment — suspension propagates to media buy health** — Syncs an audience, targets it from a package, forces ready → suspended, verifies an audience impairment, then restores ready and verifies recovery.
Flow: `sync_audiences → comply_test_controller → create_media_buy → get_media_buys → comply_test_controller → get_media_buys → comply_test_controller → get_media_buys`

**Dependency impairment cardinality — impairments[] tracks each offline resource independently** — Two creatives on two packages of the same buy. Rejects them sequentially and verifies impairments[] grows 0 → 1 → 2; recovers them sequentially via swap-assignment and verifies impairments[] shrinks 2 → 1 → 0. Pressure-tests the inverse rule under cardinality — a seller emitting any impairment entry rather than the right one fails.
Flow: `get_products → create_media_buy → sync_creatives → update_media_buy → comply_test_controller → get_media_buys → comply_test_controller → list_creatives → get_media_buys → comply_test_controller → list_creatives → get_media_buys → sync_creatives → comply_test_controller → update_media_buy → get_media_buys → sync_creatives → comply_test_controller → update_media_buy → get_media_buys`

**Seller deduplicates the same event across multiple registered event sources** — Verifies that a seller advertising conversion_tracking.multi_source_event_dedup can register multiple event sources for the same buy and deduplicate inbound events by event_id so the same conversion from a pixel and a CAPI source counts once, not twice. Sibling to media_buy_seller/performance_buy_flow gated on the dedup sub-capability bit: sellers that don't advertise multi-source dedup grade not_applicable.
Flow: `sync_accounts → get_products → sync_event_sources → create_media_buy → log_event → comply_test_controller → get_media_buy_delivery`

**Seller rejects execution after a proposal hold expires** — Verifies that an expired committed proposal returns PROPOSAL_EXPIRED instead of creating a MediaBuy.
Flow: `comply_test_controller → request_proposals → refine_proposals → comply_test_controller → accept_proposal → create_media_buy`

**Seller binds an external audience source into a targetable audience** — Verifies dataset-source ingestion, discovery echo, media-buy targeting, unsupported-rail rejection, and transport immutability.
Flow: `sync_audiences → create_media_buy → sync_audiences`

**Package frequency-cap capability and update boundaries** — Verifies constrained discovery with seller-wide unit inheritance, create-only rejection, out-of-range rejection, mutable update, package-qualified actions, and exact readback.
Flow: `sync_accounts → get_products → create_media_buy → get_media_buys → control_media_buy → get_media_buys`

**Seller honors a package-level frequency cap and reports observed frequency at-or-below the cap** — Verifies that a seller advertising frequency_capping accepts a package-level frequency_cap (max_impressions + per + window form) on create_media_buy and, after simulated delivery, reports observed reach and frequency on get_media_buy_delivery with the observed frequency at-or-below the requested cap. Runtime-enforcement scenario — no rejection arm. Sellers without frequency_capping grade not_applicable.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Seller resolves, validates, and echoes identifier-based place targeting** — Verifies known-now place discovery, declared-later place permission, create/update persistence, package-state echo, and deterministic semantic rejection cases.
Flow: `get_adcp_capabilities → get_products → create_media_buy → get_media_buys → update_media_buy → get_media_buys → create_media_buy → get_media_buys → create_media_buy → get_media_buys → create_media_buy`

**Seller proves exhaustive country-level ISO subdivision support** — Verifies that all_values is an exhaustive country claim that contains finite future region requirements.
Flow: `get_adcp_capabilities → get_products`

**Seller proves independent ISO subdivision exclusion support** — Verifies that finite subdivision exclusion support can be discovered and executed without inclusion support.
Flow: `get_adcp_capabilities → get_products → create_media_buy → get_media_buys`

**Seller validates and persists value-aware ISO subdivision targeting** — Verifies exact known-region discovery, country/value future support, independent exclusion, package readback, and deterministic rejection semantics.
Flow: `get_adcp_capabilities → get_products → get_adcp_capabilities → get_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → update_media_buy → get_media_buys → update_media_buy → get_media_buys → update_media_buy → get_media_buys → create_media_buy`

**Seller returns and completes async get_products discovery tasks** — Forces curated get_products discovery into the submitted arm, verifies idempotent replay and conflict handling, confirms the task through task APIs, completes it deterministically, and observes the terminal webhook result.
Flow: `comply_test_controller → get_products → list_tasks → comply_test_controller → get_task_status → expect_webhook`

**Seller returns a structured business rejection for a declined brief** — Forces get_products into the rejected arm and verifies its required fields, transport-success semantics, and mutual exclusion from success, partial-result, and error fields.
Flow: `comply_test_controller → get_products`

**Seller rejects an unacceptable governance-agent binding** — Verifies capability-gated governance-agent allowlisting, typed rejection, and successful recovery without persisting the rejected binding.
Flow: `sync_accounts → sync_governance → comply_test_controller → sync_governance`

**Seller commits a buy only after governance approval** — Verifies signed intent authorization, durable media-buy commit, and auditable governance outcome.
Flow: `create_media_buy → sync_plans → sync_accounts → sync_governance → get_products → check_governance → create_media_buy → get_media_buys → report_plan_outcome`

**Buyer resolves governance conditions before seller execution** — Verifies that conditions are a non-authorizing intent counterproposal and that only a later approval permits the seller to commit a media buy.
Flow: `create_media_buy → sync_plans → sync_accounts → sync_governance → get_products → check_governance → create_media_buy → get_media_buys → get_plan_audit_logs → report_plan_outcome`

**Seller rejects buy when governance denies** — Verifies that the seller rejects a media buy and propagates the denial when governance denies the transaction.
Flow: `create_media_buy → 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`

**Inline media-buy creatives without sync_creatives** — Verifies that a seller advertising inline_creative_management accepts package creatives on create_media_buy and update_media_buy without requiring the Creative Protocol or sync_creatives.
Flow: `get_adcp_capabilities → get_products → create_media_buy → update_media_buy → get_products → create_media_buy → update_media_buy → get_media_buys`

**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 a property_list reference that matches zero inventory** — Verifies a seller returns a clear unavailable-product error — not a crash — when a buyer references a property list that resolves to nothing in the seller's catalog.
Flow: `get_products → create_media_buy`

**Seller honors property_list targeting on create and update** — Verifies that a seller accepts PropertyListReference 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 accepts supported measurement_terms** — A seller that advertises measurement-term acceptance accepts a supported configuration and returns the confirmed terms.
Flow: `get_products → create_media_buy`

**Seller rejects unworkable measurement_terms** — Buyer proposes measurement_terms the seller will not accept; seller returns TERMS_REJECTED without being required to accept a different configuration.
Flow: `get_products → create_media_buy`

**Seller accepts and reads back a MediaBuy frequency cap** — Verifies participation-gated discovery, incompatible-mix rejection, exact acceptance, and readback of a root cap across a two-product buy.
Flow: `sync_accounts → get_products → create_media_buy → get_media_buys`

**Seller solves for budget from a structured outcome target** — Verifies criteria.outcome_target reverse forecasting: a metric or event goal plus desired volume answered with total_budget_guidance on proposals and a forecast whose points carry the goal's key in metrics.
Flow: `sync_accounts → request_proposals`

**Seller declares owner-sold channel carriage and enforces collection selector boundaries** — Verifies an owner-sold channel product combines host property scope with the owner's canonical collection selector, that buyer collection selection requires explicit collection_ids, and that unknown or mismatched collection selectors are rejected rather than silently substituted.
Flow: `get_products → create_media_buy`

**Seller preserves explicit package automatic bidding** — Verifies package-scoped automatic bidding independently of media-buy bidding support.
Flow: `sync_accounts → create_media_buy → get_media_buys`

**Legacy package correlation fallback without product_id** — Models the mixed-seller compatibility path where a legacy package response omits product_id and buyers correlate by package context.buyer_ref.
Flow: `get_media_buys`

**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 surfaces per-creative conversion attribution in delivery reporting** — Verifies that a seller advertising conversion_tracking.per_creative_attribution can register two creatives on a single package, accept a media buy with a CPA event-kind goal, ingest conversion events, and surface a populated by_package[].by_creative[] breakdown carrying conversions per creative in get_media_buy_delivery. Sibling to media_buy_seller/performance_buy_flow gated on the per_creative_attribution sub-capability: sellers that report attribution only at the line / package / placement / campaign granularity (retail-media, MMP-mediated mobile, CTV performance) grade not_applicable, not failing.
Flow: `sync_accounts → get_products → sync_event_sources → sync_creatives → create_media_buy → log_event → comply_test_controller → get_media_buy_delivery`

**Seller fulfills a performance (event-kind goal) media buy with a CPA target** — Verifies that a seller advertising conversion_tracking can bind an event source, accept a media buy with an event-kind optimization_goal targeting CPA, reject malformed performance briefs, ingest conversion events, and report conversion-attributed delivery including per-creative breakdown. ROAS / maximize_value goals are out of scope — see media_buy_seller/performance_buy_flow_roas (separate scenario, gated on the supported_target_kinds capability bit from #4639).
Flow: `sync_accounts → get_products → sync_event_sources → create_media_buy → log_event → comply_test_controller → get_media_buy_delivery`

**Seller fulfills a performance (event-kind goal) media buy with a ROAS target** — Verifies that a seller advertising conversion_tracking with per_ad_spend in supported_targets can accept a media buy whose event-kind optimization_goal carries a ROAS (per_ad_spend) target bound to an event source with value_field, reject ROAS goals that omit value_field on every event-source entry, ingest valued purchase events, and report conversion_value + roas alongside conversions and cost_per_acquisition. Sibling to media_buy_seller/performance_buy_flow gated on the supported_targets sub-capability: sellers that don't advertise per_ad_spend (most broadcast TV, upper-funnel video, signal-only) grade not_applicable.
Flow: `sync_accounts → get_products → sync_event_sources → create_media_buy → log_event → comply_test_controller → get_media_buy_delivery`

**Seller declares brief-routing portfolio scope** — Advises when a media-buy seller omits its complete country or channel routing allowlist.
Flow: `get_adcp_capabilities → get_products`

**Seller filters products by accepted pricing currencies** — Verifies that get_products filters.pricing_currencies returns only products buyable in the requested media pricing currency and prunes returned product pricing_options.
Flow: `get_products`

**Product coverage is preserved across discovery surfaces** — Checks legacy and compact coverage predicates against independent negative controls without configuring delivery targeting.
Flow: `get_products → list_products`

**Seller applies deterministic product filters in every buying mode** — Verifies schema-valid get_products filters affect brief curation, wholesale feeds, and refinement instead of silently no-oping.
Flow: `get_products`

**Seller exposes wholesale signal options and honors package-level signal_targeting_groups** — Verifies that a seller with wholesale products and wholesale get_signals can expose signal targeting eligibility, accept package-level signal_targeting_groups with pricing, reject unknown signals, and echo the applied grouped expression on readback.
Flow: `get_products → get_signals → create_media_buy → get_media_buys → create_media_buy`

**Seller handles proposal refinement and finalize** — Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and execute via create_media_buy.
Flow: `sync_accounts → get_products → create_media_buy → get_products → create_media_buy`

**Seller handles proposal finalize — asap start_time form** — Variant of proposal_finalize that exercises start_time: 'asap' on create_media_buy, catching wrapper-layer rejections of the spec-defined string literal form.
Flow: `sync_accounts → get_products → create_media_buy`

**Seller returns canonical proposal error codes** — Validates that the seller returns PROPOSAL_NOT_FOUND (and PROPOSAL_EXPIRED) rather than generic NOT_FOUND or INVALID_REQUEST when the buyer references proposal IDs that do not exist or have expired.
Flow: `sync_accounts → get_products → create_media_buy`

**Seller fulfills a media buy with a reach optimization goal** — Verifies that a seller advertising reach in supported_optimization_metrics can accept a media buy whose metric-kind optimization_goal targets unique reach with a buyer-supplied reach_unit, reject reach_unit values not in the product's metric_optimization.supported_reach_units, and report reach + frequency on delivery. Sibling to the performance buy scenarios on the brand-reach side: sellers that don't advertise reach as an optimization metric (most pure performance DSPs, retail-media networks) grade not_applicable.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery → create_media_buy → comply_test_controller → get_media_buy_delivery → create_media_buy → comply_test_controller → get_media_buy_delivery → create_media_buy → comply_test_controller → get_media_buy_delivery → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Seller enforces refine[] finalize-exclusivity and MULTI_FINALIZE_UNSUPPORTED** — Validates that sellers reject mixed-finalize requests (INVALID_REQUEST), structurally-invalid finalize entries, and handle multi-proposal finalize atomically or reject with MULTI_FINALIZE_UNSUPPORTED.
Flow: `sync_accounts → get_products`

**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`

**Revenue-share pricing and settlement** — Verifies contingent revenue-share discovery, commitment without a bid, commissionable-value delivery, and formula-checked usage reconciliation.
Flow: `sync_accounts → sync_event_sources → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery → report_usage`

**Seller accepts and persists a seller-optimized budget across packages** — Verifies that a seller advertising seller_optimized_budget accepts and persists a shared total with cross-package goals, package caps, a soft minimum-spend target, and two-level pacing. When media-buy cost caps are also advertised, verifies that the control binds to the allocation goal.
Flow: `sync_accounts → get_products → create_media_buy → get_media_buys → create_media_buy → get_media_buys → get_products → create_media_buy → get_media_buys`

**Seller resolves targeting during discovery and persists inventory selection** — Verifies targeting-scoped discovery forecasts, future overlay support, sparse configured-product modifications, opaque product acceptance, and inventory targeting across create/update/readback.
Flow: `get_products → create_media_buy → update_media_buy → get_media_buys → update_media_buy → create_media_buy → get_products → create_media_buy → update_media_buy → get_media_buys → update_media_buy → get_media_buys`

**Atomic media-buy total budget redistribution** — Verifies that a fixed media-buy total_budget update proportionally rescales active packages, returns auditable package state, and rejects competing package patches atomically.
Flow: `sync_accounts → create_media_buy → update_media_buy → get_media_buys → update_media_buy`

**Seller handles typed proposal negotiation with constraints, product changes, and alternatives** — Verifies the AdCP 3.2 typed negotiation lifecycle: capability-gated constraint satisfaction, product changes, digest-verified alternatives, partial invariant, immutable lineage, finalize atomicity, and idempotent replay through refine_proposals.
Flow: `get_adcp_capabilities → sync_accounts → get_products → refine_proposals → accept_proposal → refine_proposals`

**Vendor-metric external catalog precondition** — Exercises the 3.1 SHOULD-window behavior for vendor_metric goals whose metric_id is absent from a seeded measurement.metrics[] catalog.
Flow: `sync_accounts → comply_test_controller → create_media_buy`

**Vendor-metric optimization-goal: acceptance and rejection** — Verifies that a seller supporting vendor_metric_optimization accepts a media buy whose optimization_goal has kind 'vendor_metric' when all preconditions are met, and rejects goals that fail either the capability check or the reporting-coherence check.
Flow: `sync_accounts → get_products → create_media_buy`

**Media buy state machine lifecycle** — Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement.
Flow: `get_adcp_capabilities → get_products → sync_creatives → 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 → sync_governance → get_products → create_media_buy → sync_creatives → get_media_buy_delivery`

**Governance-aware seller** — Seller agent that composes with a campaign-governance agent after baseline sync_governance registration — verifies approved intent at execution and blocks denied or invalid execution verdicts. Optional claim for the full governance-check loop.
Flow: `get_adcp_capabilities`

**Seller registers one governance agent and rejects multiple agents** — Verifies valid single-agent registration and rejection of a governance_agents payload that violates the maxItems: 1 constraint.
Flow: `sync_accounts → sync_governance`

**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 → sync_governance → create_media_buy → get_media_buys → 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 → sync_governance → sync_catalogs → build_creative → expect_substitution_safe → get_products → create_media_buy → sync_event_sources → log_event → provide_performance_feedback → get_media_buy_delivery`

**Digital out-of-home — non-guaranteed** — DOOH seller for non-guaranteed venue and screen inventory with canonical screen formats and substantive play reporting.
Flow: `get_adcp_capabilities → list_products → create_media_buy → validate_input → sync_creatives → create_media_buy → comply_test_controller → 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 → sync_creatives → get_media_buy_delivery`

**get_products wholesale pagination cursor integrity** — Validates get_products wholesale-mode pagination by walking a seeded product feed from continuation to terminal, while documenting that brief/refine pagination caps returned products in curated results.
Flow: `get_adcp_capabilities → get_products`

**Wholesale product feed webhook registration** — Validates account-level notification_configs[] registration for agents that advertise product wholesale feed webhook events.
Flow: `sync_accounts`

**Wholesale product feed cache-scope isolation** — Validates that get_products conditional-fetch is keyed by cache_scope: a wholesale_feed_version minted under one cache_scope must not short-circuit (unchanged: true) a request the seller resolves to a different cache_scope.
Flow: `get_products`

**Wholesale product feed versioning** — Validates get_products wholesale feed versioning: bootstrap responses carry wholesale_feed_version/cache_scope and matching if_wholesale_feed_version probes return unchanged without product rows.
Flow: `get_products`

### Reporting

**Seller narrows metrics and reports delivery by canonical format** — Verifies requested-metric narrowing, time-threshold video views, negotiated format breakdowns, truncation disclosure, and applied-sort echoes.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Billing finality delivery and usage reporting** — Verifies that 3.1 billing-grade delivery rows distinguish provisional from final numbers and that report_usage accepts final usage records with finality metadata.
Flow: `sync_accounts → create_media_buy → comply_test_controller → get_media_buy_delivery → comply_test_controller → get_media_buy_delivery → report_usage`

**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 → create_media_buy → comply_test_controller → get_media_buy_delivery`

**End-to-end metric accountability through the media buy lifecycle** — Buyer requires specific reporting metrics at discovery; seller filters products to those that can deliver; delivery report exposes any gaps via missing_metrics.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Container-subsumption evaluation for metric set operations** — Verifies a container token (e.g. viewability) subsumes its leaf identities (e.g. viewable_rate) for required_metrics filtering and requested_metrics carrier selection, and that a leaf never subsumes a sibling leaf.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery → get_products`

**Seller applies deterministic media-buy read filters** — Verifies get_media_buys membership filters and half-open get_media_buy_delivery date bounds instead of accepting them as no-ops.
Flow: `get_media_buys → comply_test_controller → get_media_buy_delivery`

**End-to-end vendor-metric accountability: declaration → commitment → delivery audit** — Buyer commits vendor metrics on two packages, then verifies one clean package, one overdue gap, and one not-yet-measurable omission.
Flow: `sync_accounts → get_products → create_media_buy → comply_test_controller → get_media_buy_delivery`

**Reliable Reporting: Managed Delivery** — Validates the Managed Delivery tier's explicit capability, atomic managed offering, resource retention, and authorization-revocation contract.
Flow: `get_adcp_capabilities → comply_test_controller → get_reporting_status → comply_test_controller`

**Reliable Reporting: Reconciled Billing** — Validates the Reconciled Billing tier's canonical evidence and authenticated receipt path for official revisions and later adjustments.
Flow: `get_adcp_capabilities → comply_test_controller → get_reporting_status → sync_reporting_receipts → get_reporting_status → comply_test_controller → get_reporting_status → sync_reporting_receipts → get_reporting_status → sync_reporting_receipts → get_reporting_status`

### Products

**Canonical formats and deprecated named-format compatibility** — Validates canonical product formats and the optional, explicitly deprecated named-format compatibility projection.
Flow: `get_products → create_media_buy`

### Signals

**Signals baseline** — Baseline domain storyboard — every signals agent must declare signals support and return discoverable signals.
Flow: `get_adcp_capabilities → get_signals`

**Signals agent returns and completes async get_signals discovery tasks** — Forces semantic get_signals discovery into the submitted arm, verifies task visibility, completes it deterministically, and observes the terminal webhook result.
Flow: `comply_test_controller → get_signals → list_tasks → comply_test_controller → get_task_status → expect_webhook`

**Marketplace signal agent** — Signal agent that resells third-party data provider signals with verifiable provider-published provenance.
Flow: `get_adcp_capabilities → get_signals → activate_signal`

**Signal agent accepts a signed activation authorization** — Verifies that a valid task- and payload-bound governance token permits paid signal activation and that the deployment persists.
Flow: `activate_signal → sync_accounts → sync_governance → get_signals → sync_plans → check_governance → activate_signal → get_signals`

**Signal agent rejects activation without governance approval** — Verifies that a signal agent claiming activate_signal governance enforcement rejects missing authorization without calling a deployment platform.
Flow: `activate_signal → sync_accounts → sync_governance → get_signals → activate_signal`

**Owned signal agent** — Signal agent serving first-party or proprietary audience data for discovery without external catalog verification.
Flow: `get_adcp_capabilities → get_signals`

**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`

**Wholesale signals feed webhook registration** — Validates account-level notification_configs[] registration for agents that advertise signal wholesale feed webhook events.
Flow: `sync_accounts`

**Wholesale signals feed cache-scope isolation** — Validates that get_signals conditional-fetch is keyed by cache_scope: a wholesale_feed_version minted under one cache_scope must not short-circuit (unchanged: true) a request the agent resolves to a different cache_scope.
Flow: `get_signals`

**Wholesale signals feed versioning** — Validates get_signals wholesale feed versioning: bootstrap responses carry wholesale_feed_version/cache_scope and matching if_wholesale_feed_version probes return unchanged without signal rows.
Flow: `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`

**Sponsored context accountability** — Validates that a brand-side SI agent claiming sponsored_context_accountability conformance emits sponsored_context on each response, accepts well-formed sponsored_context_receipt envelopes, and rejects receipts that silently downgrade declared context_use.
Flow: `si_initiate_session → si_send_message`

**Sponsored intelligence** — Specialism claim for agents that expose conversational sponsored experiences via the SI session lifecycle. Preview while the underlying SI tools remain `x-status: experimental`; SDK dispatch parity with other specialism IDs.

### 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_governance → sync_audiences → sync_creatives → sync_catalogs → sync_creatives → sync_catalogs → sync_creatives → preview_creative → expect_substitution_safe → 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 → update_rights → acquire_rights`

**Account identity reconciliation** — Validates revision-guarded operator identity rekeying, collision safety, and former-key redirects for sellers that advertise identity updates.
Flow: `get_adcp_capabilities → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts → comply_test_controller → sync_accounts → list_accounts → get_media_buys → sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts`

**Agent notification config lifecycle** — Validates the 3.2 agent-level capabilities.changed registration surface: advertised revision fencing, NO_AUTH rejection, and dry-run clear semantics.
Flow: `get_adcp_capabilities → sync_agent_notification_configs`

**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, fresh keys create new resources, and concurrent retries with the same key produce exactly one resource (first-insert-wins under rule 9).
Flow: `get_adcp_capabilities → create_media_buy → get_media_buys → expect_rate_limit_not_replayed`

**Notification config event-scope rejection** — Validates that sync_accounts.accounts[].notification_configs[] rejects media-buy-anchored notification types.
Flow: `sync_accounts`

**Notification config lifecycle** — Validates account-level notification_configs[] lifecycle behavior on sync_accounts: paused registration, durable echo, subscriber-keyed replacement, and clear.
Flow: `sync_accounts → list_accounts → sync_accounts → list_accounts → sync_accounts → list_accounts`

**Notification config semantic rejections** — Validates account-level notification_configs[] semantic rejection for duplicate subscriber keys.
Flow: `sync_accounts`

**Deprecated list_creative_formats compatibility — pagination cursor integrity** — When an agent still exposes deprecated list_creative_formats compatibility, validates its cursor↔has_more invariant by seeding two formats and walking pages with max_results=1.
Flow: `get_adcp_capabilities → comply_test_controller → list_creative_formats`

**Pagination continuation integrity — list_accounts** — Validates list_accounts pagination by seeding sandbox accounts, requesting a small first page, and following its cursor once.
Flow: `get_adcp_capabilities → comply_test_controller → 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`

**Principal configuration** — Validates the experimental stable-principal-scoped configuration surface: capability discovery, pre-configuration identity readback, NO_AUTH rejection, dry-run setup, applied lifecycle with readback, idempotent replay, and section clearing.
Flow: `get_adcp_capabilities → get_principal → sync_principal → get_principal → sync_principal → get_principal → sync_principal → get_principal`

**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`

**Read-tool idempotency_key envelope tolerance** — Validates that read-only AdCP tasks and the 3.x get_products compatibility facade tolerate an optional idempotency_key without claiming read-response replay.
Flow: `get_adcp_capabilities → get_products → list_accounts → list_creative_formats → list_creatives → get_adcp_capabilities`

**Reliable Reporting: consumer-status loop** — Validates opt-in buyer-to-seller reporting status, missing-obligation identity, caller-scoped mismatch, and immutable supersession.
Flow: `comply_test_controller → sync_reporting_status → get_reporting_status → comply_test_controller → sync_reporting_status → comply_test_controller → get_reporting_status → get_media_buy_delivery → sync_reporting_status → get_reporting_status → comply_test_controller → get_reporting_status → comply_test_controller → get_reporting_status → sync_reporting_status → get_reporting_status`

**Reliable Reporting: Core declaration** — Validates the proper-name Reliable Reporting 1.0 declaration and its required Core tier without requiring optional tiers or a sandbox controller.
Flow: `get_adcp_capabilities`

**Reliable Reporting: Core lifecycle** — Validates reporting.core obligation-before-report behavior, clock-derived health, and explicit zero-row reporting.
Flow: `comply_test_controller → get_reporting_status → comply_test_controller → get_reporting_status → comply_test_controller → get_reporting_status → comply_test_controller → get_reporting_status → get_media_buy_delivery → comply_test_controller → get_reporting_status → get_media_buy_delivery → comply_test_controller → get_reporting_status → comply_test_controller → get_reporting_status`

**Schema compliance — signals protocol** — Validates that signals agent responses conform to AdCP schemas with all required fields present and correctly typed.
Flow: `get_adcp_capabilities → get_signals`

**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 → 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`

**Release-precision version negotiation** — Sellers advertise supported releases on capabilities and echo the served release on every response.
Flow: `get_adcp_capabilities`

**Webhook emission — outbound webhook conformance (operation ID + signing + idempotency + sync silence)** — Any agent that emits webhooks MUST echo the caller-supplied operation_id in every payload, carry a stable idempotency_key across retries, avoid duplicate webhook side effects on request replay, and — when the buyer has not opted into the deprecated HMAC fallback — MUST sign deliveries under the RFC 9421 webhook profile. Sellers MUST NOT emit task webhooks for inline terminal responses; any future sync-completion notification mode would be explicit and capability-advertised. Graded by a runner hosting a webhook receiver during storyboard execution.
Flow: `get_adcp_capabilities → expect_webhook → get_products → expect_no_webhook → get_products → expect_webhook_retry_keys_stable → fetch_brand_jwks → assert_jwks_purpose → expect_webhook_signature_valid`

**Webhook receiver envelope: inbound POST conformance** — Buyer webhook receivers MUST accept full MCP webhook envelopes, reject bare delivery-result payloads, preserve raw-body signature verification, and dedupe retries by a stable idempotency_key.
Flow: `replay_webhook_vector`

**Wholesale feed bulk-change webhook registration** — Validates account-level notification_configs[] registration for agents that advertise wholesale_feed.bulk_change webhook events.
Flow: `sync_accounts`

### Media buy buyer

**Buyer agent media buy creation and idempotent activation** — Buyer agent that creates media buys with correct idempotency keys, handles transient failures with retry, and coordinates creative assignment.
Flow: `create_media_buy → get_task_status → sync_creatives`

**Buyer agent product discovery and candidate selection** — Buyer agent that discovers seller inventory, interprets product capabilities, and assembles a candidate shortlist from a brief.
Flow: `get_adcp_capabilities → get_products → list_products → get_products`

**Buyer agent delivery monitoring and pacing response** — Buyer agent that monitors delivery via webhooks and polling, detects variance, and responds to pacing drift.
Flow: `get_media_buy_delivery → get_media_buys`

**Buyer agent proposal negotiation and terms handling** — Buyer agent that requests proposals, refines terms, handles TERMS_REJECTED, and accepts or declines offers.
Flow: `request_proposals → refine_proposals → accept_proposal`

**Buyer agent error recovery and resilience** — Buyer agent that handles seller offline, auth expiry, stale digest, idempotency collisions, and rate limiting.
Flow: `get_products → create_media_buy`

**Orchestrator multi-seller fan-out and reconciliation** — Orchestrator agent that coordinates across multiple sellers, handles partial failures, and reconciles cross-agent state.
Flow: `get_products → create_media_buy`

### 

**Generative creative agent** — Agent that takes a brief and generates finished creatives from scratch — no input assets required.
Flow: `get_adcp_capabilities → build_creative → sync_catalogs → build_creative → sync_catalogs → build_creative → expect_substitution_safe`

**Account-scoped creative transformer agent** — Agent that exposes account-scoped transformers (voices, models, render configs) via list_transformers, then builds and refines creatives by transformer_id + typed config — with variant fan-out and strict config validation.
Flow: `get_adcp_capabilities → list_transformers → build_creative`

**Buyer fixture publisher contract** — Contract for the runner-side reference sell-side agent used by buyer storyboards.

**Buyer-orchestrator compliance track** — Certification levels for buyer and orchestrator agents: basic (discovery + activation), standard (+ negotiation + monitoring), advanced (+ recovery + multi-agent orchestration).

**Runner output contract** — Required failure-detail shape that AdCP storyboard runners MUST emit so implementors can self-diagnose validation failures.

### Error Handling

**Billing contract and gate dispatch — sync_accounts capability vs per-buyer-agent gate** — Validates that account.supported_billing is declared and honored by sync_accounts, including out-of-set BILLING_NOT_SUPPORTED rejection, recovery-value consistency, and the distinct per-buyer-agent BILLING_NOT_PERMITTED_FOR_AGENT gate.
Flow: `get_adcp_capabilities → sync_accounts`

**Error handling — signals protocol** — Validates that signals agents return properly structured AdCP errors with correct codes, recovery hints, and transport bindings.
Flow: `get_adcp_capabilities → activate_signal → get_signals → activate_signal`

**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`

**STALE_RESPONSE advisory-success wire placement** — Validates that STALE_RESPONSE rides in errors[] on a populated success response with transport success preserved, and that STALE_RESPONSE is absent on healthy upstream responses.
Flow: `get_adcp_capabilities → comply_test_controller → get_products`

### Security

**Comply test controller — live-account denial gate** — Verifies that a seller exposing comply_test_controller refuses calls from live-mode (non-sandbox) accounts with FORBIDDEN.
Flow: `comply_test_controller`

**Authentication baseline** — Every AdCP agent MUST require authentication on protected operations. At least one of static credentials or OAuth MUST be implemented and correctly advertised.

### Security transport

**OAuth discovery and metadata consistency** — Capability-gated evaluation of an agent's RFC 9728 protected-resource metadata and the complete RFC 8414 authorization-server graph it advertises.
Flow: `get_adcp_capabilities`

**Signed requests — RFC 9421 transport-layer verification** — Agent verifies RFC 9421 HTTP Signatures on incoming AdCP requests per the 3.1-compatible transport-layer profile. Universal capability-gated storyboard — runs for any agent advertising `request_signing.supported: true` regardless of `supported_protocols`. Graded against the legacy wire suite covering every 3.1 checklist step and canonicalization-edge rule.
Flow: `get_adcp_capabilities`

**Trusted Match publisher authentication rejection** — Verifies that a TMP router deployment rejects schema-valid Context Match and Identity Match requests with absent or invalid publisher authentication using HTTP 401 and a WWW-Authenticate challenge.
Flow: `trusted_match_missing_auth_context_probe → trusted_match_invalid_auth_context_probe → trusted_match_missing_auth_identity_probe → trusted_match_invalid_auth_identity_probe`

## 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_IDENTITY_CONFLICT` | correctable | The complete operator_identity requested through sync_accounts would rekey the account onto a natural key already owned by another account. The seller MUST reject the change atomically, MUST NOT merge the accounts, and MUST preserve both accounts and every account-scoped resource unchanged. Distinct from CONFLICT, which reports a transient stale revision or concurrent write. |
| `ACCOUNT_MOVED` | correctable | An authorized caller used a former natural key that was tombstoned after the same account was rekeyed through sync_accounts. The seller MUST return the current canonical reference in error.details.current_account, conforming to error-details/account-moved.json, and MUST NOT provision or resolve a second account from the former key. Sellers retain the tombstone while the account or any account-scoped historical resources are retained. To avoid a cross-tenant existence oracle, sellers return ACCOUNT_MOVED only when the caller is authorized to resolve the current account; otherwise they return ACCOUNT_NOT_FOUND. |
| `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_REQUIRED` | correctable | The service must resolve the commercial account before it can determine governance applicability, but the request and referenced resource do not identify one. |
| `ACCOUNT_SETUP_REQUIRED` | correctable | Natural key resolved but the account needs setup before use. |
| `ACCOUNT_SUSPENDED` | terminal | Account has been suspended. |
| `ACTION_NOT_ALLOWED` | correctable | The requested mutation maps to an action that is not currently available on this media buy. Sellers MUST populate `error.details` with `attempted_action` (the structured available-action identifier the request maps to), `reason` (an `action-not-allowed-reason` value: `wrong_status`, `not_supported_on_product`, `not_supported_on_buy`, `mode_mismatch`, or `condition_unresolved`), and `currently_available_actions` (echo of the buy's resolved `available_actions[]` so the buyer SDK can offer recovery without a separate get_media_buys round-trip). |
| `AGENT_BLOCKED` | terminal | The calling buyer agent's commercial relationship with the seller is permanently denied — the agent is blocked. Sibling to `AGENT_SUSPENDED` on the agent-relationship axis but with no recovery path (a suspension may lift via re-onboarding; a block does not). The code itself is the discriminator — same posture as `AGENT_SUSPENDED`: no `error.details` payload, no per-agent commercial state, cross-tenant onboarding oracle clamp + channel-coverage requirements normative in error-handling.mdx Per-Agent Authorization Gate. |
| `AGENT_SUSPENDED` | terminal | The calling buyer agent's commercial relationship with the seller is temporarily paused — the agent is onboarded but currently suspended. Sibling to `ACCOUNT_SUSPENDED` (account-wide) and `CAMPAIGN_SUSPENDED` (per-plan) but scoped to the agent-relationship axis (orthogonal to any specific account on that agent). The code itself is the discriminator — it does NOT carry an `error.details` payload (mirroring `BILLING_NOT_PERMITTED_FOR_AGENT`'s discriminator-by-code pattern), and MUST NOT carry per-agent commercial state (rate cards, payment terms, credit limit, billing entity, contact channels) since full disclosure of per-agent state in a single probe is a per-agent oracle. Cross-tenant onboarding oracle clamp + channel-coverage requirements (response shape, HTTP/A2A/MCP status, headers, side effects, observability, latency parity, retry-counter side channel) are normative in error-handling.mdx Per-Agent Authorization Gate; this description does not restate them to avoid drift. |
| `AMBIGUOUS_BIDDING_POLICY` | correctable | The same effective package combines the canonical bidding block with legacy bid_price or monetary optimization-goal target fields, so two bidding interpretations are present. Sellers MUST reject rather than choosing a winner. |
| `AUDIENCE_TOO_SMALL` | correctable | Audience segment is below the minimum required size for targeting. |
| `AUTH_INVALID` | terminal | Credentials were presented but rejected — revoked, expired, malformed signature, or a key no longer in the seller's keystore. Sellers MUST return this code when an `Authorization` header was present but verification failed. SDK server runtime treats this code as terminal and does not refresh or retry it; use `AUTH_MISSING` / legacy `AUTH_REQUIRED` for missing request credentials that can be refreshed via `AccountStore.refreshToken`. |
| `AUTH_MISSING` | correctable | No credentials were presented. Sellers MUST return this code when no `Authorization` header was included in the request. |
| `AUTH_REQUIRED` | correctable | **Deprecated** — use `AUTH_MISSING` (no credentials presented) or `AUTH_INVALID` (credentials presented and rejected). Retained as a backward-compatible alias during the 3.x deprecation window. |
| `AUTHORIZATION_REQUIRED` | correctable | The caller is authenticated, but the referenced object requires an additional downstream platform connection, identity, creator, or post authorization before the seller can complete the requested action. Typical use: `sync_creatives` with a `published_post` reference where the seller can resolve the post but the owning identity has not authorized paid serving, or authorization has expired/revoked and can be restored. Distinct from `AUTH_MISSING` / `AUTH_INVALID` (caller credentials) and from `PERMISSION_DENIED` (seller policy denies the caller). Sellers SHOULD include recovery details conforming to `error-details/authorization-required.json`, especially `error.details.missing_connections[]` when the caller needs to complete one of several platform connections. Legacy recovery hints such as `authorization_url`, `authorization_instructions`, or `reference_authorization` remain valid when safe to disclose. |
| `BIDDING_PLACEMENT_CONFLICT` | correctable | The authored media-buy/package bidding scopes cannot be represented by the provider's native campaign, package, or shared-strategy placement rules. Sellers MUST detect this before any provider mutation and SHOULD identify the conflicting scopes and provider constraint in error.details. |
| `BILLING_NOT_PERMITTED_FOR_AGENT` | correctable | The seller's `supported_billing` capability accepts the requested model, but the calling buyer agent's commercial relationship with the seller does not — e.g., the agent is onboarded as passthrough-only (no payments relationship — only the operator can be invoiced) and `billing: 'agent'` or `billing: 'advertiser'` is rejected even though the seller supports both at the capability level. Distinct from `BILLING_NOT_SUPPORTED` (seller-wide capability) by being narrowly per-buyer-agent: the gate is the seller's onboarding record for this caller, not the seller's global wire capability. Sellers MUST emit this code only after agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; callers without established identity MUST receive `BILLING_NOT_SUPPORTED` instead, to prevent the distinct code from acting as an onboarding oracle. The recovery shape is deliberately minimal — `error.details` MUST conform to `error-details/billing-not-permitted-for-agent.json` (`rejected_billing` plus an optional single `suggested_billing` retry value, typically `operator`) and MUST NOT carry the agent's full permitted-billing subset, rate cards, payment terms, credit limit, billing entity, or any other per-agent commercial state. |
| `BILLING_NOT_SUPPORTED` | correctable | The seller declines the requested `billing` value either at the seller-wide capability level (`supported_billing` does not include the value) or at the per-account-relationship level (e.g., the seller accepts `operator` billing in general but has no direct billing relationship with the operator on this specific account). The default reject code for billing-value mismatches; `error.details` SHOULD conform to `error-details/billing-not-supported.json` (`scope` ∈ `{"capability", "account"}` plus optional `supported_billing` echo for the `"capability"` scope) so callers can dispatch without parsing prose. Distinct from `BILLING_NOT_PERMITTED_FOR_AGENT`, which is narrowly scoped to the calling buyer agent's commercial relationship with the seller (passthrough-only vs agent-billable) rather than to the seller's capability or per-account state. Sellers MUST emit `BILLING_NOT_PERMITTED_FOR_AGENT` only when agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; in all other cases (unauthenticated callers and bearer credentials not mapped to a specific agent record) sellers MUST return `BILLING_NOT_SUPPORTED` and MUST omit `error.details.scope` — emitting the per-agent code or the `"account"`-scope hint without established identity is a cross-tenant onboarding oracle (same uniform-response shape required by the `*_NOT_FOUND` family). |
| `BILLING_OUT_OF_BAND` | terminal | A creative-agent billing-loop operation (`report_usage` is the canonical case) received a well-formed record that the agent will not bill on because this account bills via a non-AdCP channel — flat license, SaaS contract, bundled enterprise agreement, or any other out-of-band arrangement. The agent returns `accepted: 0` with the offending record(s) listed in `errors[]` carrying this code; the request itself is valid and silent acceptance would break buyer-side reconciliation. Distinct from `BILLING_NOT_SUPPORTED` (the seller declines a specific `billing` value on a media-buy account where AdCP billing is otherwise in scope) and `BILLING_NOT_PERMITTED_FOR_AGENT` (per-buyer-agent commercial gate on an otherwise-billable surface) by signaling that the entire billing surface is offline for this account, not that a specific value or caller is rejected. Buyers SHOULD pre-filter by reading `capabilities.creative.bills_through_adcp` from `get_adcp_capabilities` before issuing `report_usage`; agents that have not yet declared the capability remain in the probe-to-discover mode. The error is returned per-record (in the `report_usage` response `errors[]` array with `field` pointing at `usage[N]` or a specific record subpath), not at the envelope level. The code itself is the discriminator; no `error.details` shape is defined for this code (mirroring `CONFIGURATION_ERROR`'s discriminator-by-code pattern). |
| `BRAND_REQUIRED` | correctable | A billable operation was attempted without a brand reference. Every billable operation requires either a seller-assigned `account_id` or a natural key including `brand`. |
| `BUDGET_CAP_REACHED` | correctable | build_creative stopped producing early because the next leaf would exceed the request's max_spend ceiling. Normally a SUCCESSFUL partial build (BuildCreativeVariantSuccess with budget_status: 'capped' and an advisory BUDGET_CAP_REACHED entry — every returned leaf is real and billed); returned as a terminal error only when even the first leaf would exceed the cap (no partial possible). Distinct from BUDGET_EXCEEDED (would exceed a media-buy/package allocation — a rejection) and BUDGET_EXHAUSTED (already spent). |
| `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. |
| `CATALOG_LIMIT_EXCEEDED` | correctable | The account has reached its maximum catalog count. |
| `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. |
| `CONFIGURATION_ERROR` | terminal | The seller's deployment is misconfigured in a way that prevents handling the request — the buyer cannot fix it, retrying will not help, and reporting to the seller's operator is the only remediation. Examples: account declared with `mode: 'mock'` but no `mock_upstream_url` populated; platform declared with `mode: 'live'` or `mode: 'sandbox'` but no `upstream_url` declared; required environment variable unset on the seller process. Distinct from `INVALID_REQUEST` (buyer-fixable; the request itself is malformed), `SERVICE_UNAVAILABLE` (transient; retry-with-backoff may succeed), `UNSUPPORTED_FEATURE` (capability mismatch — the seller does not implement the requested specialism), `ACCOUNT_SETUP_REQUIRED` (buyer-side onboarding incomplete; this code is seller-side deployment incomplete), and `GOVERNANCE_UNAVAILABLE` (governance-agent-scoped; transient). Wire placement. The deployment cannot produce a success artifact, so sellers MUST flip transport-level failure markers (HTTP 5xx, MCP `isError: true`, A2A `failed`) and populate both layers per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. The code itself is the discriminator; no `error.details` shape is defined for this code (mirroring the minimal-disclosure precedent of `AGENT_SUSPENDED` / `AGENT_BLOCKED`). Sellers SHOULD populate `error.message` with operator-actionable detail (which metadata key is missing, which env var is unset) and MUST NOT include credentials, connection strings, or stack traces — the message is wire-visible to the buyer. |
| `CONFLICT` | transient | Concurrent modification detected. The resource was modified by another request between read and write. |
| `CONFLICTING_SELECTORS` | correctable | A 3.x package request carries multiple resolvable format selector routes that select different canonical product format declaration sets. Sellers MUST first resolve every present route independently (`format_option_refs`, direct `format_kind` plus `params`, and deprecated `format_ids`) without applying precedence. An unresolved option reference or an unprojectable legacy ID is rejected with `UNSUPPORTED_FEATURE`, not this code. Once all routes resolve, sellers derive the product `format_options[]` entries selected by each route using directional product satisfaction and require those sets to match. Legacy parameter compatibility uses the asymmetric v2-narrows-v1 relation defined by canonical formats, not raw object equality. For fixed-size image selectors, both width and height participate in compatibility. Sellers MUST reject different format shapes, selected option sets, or incompatible dimensions rather than silently choosing the canonical route. `error.field` SHOULD point at the conflicting package, and `error.details` SHOULD identify the supplied selector routes and their normalized canonical summaries. |
| `CREATIVE_DEADLINE_EXCEEDED` | correctable | Creative change submitted after the package's creative_deadline. Distinct from CREATIVE_REJECTED (content-policy, brand-safety, or accessibility-review failure). |
| `CREATIVE_INACCESSIBLE` | correctable | A creative governance agent (get_creative_features) could not retrieve the submitted creative_manifest assets for evaluation — an asset URL was unreachable, returned an error, or required credentials the agent does not hold. Distinct from CREATIVE_NOT_FOUND (a creative_id absent from the agent's library, not an asset-fetch failure) and CREATIVE_REJECTED (assets retrieved but failed creative review). |
| `CREATIVE_LOCALE_NOT_ACCEPTED` | correctable | A creative bound to a locale-constrained product format has no materialized variant matching locale_policy.accepted_language_ranges, lacks protocol-declared locale topology, or uses serve_default with a seller-ineligible default variant. Seller ranges use RFC 4647 Basic Filtering and are applied independently for every placement where the assignment may serve, before buyer Lookup, locale_fallbacks, or default selection. Distinct from CREATIVE_REJECTED because this is a mechanically discoverable assignment-eligibility mismatch, not subjective content review. error.field SHOULD point to the offending creative or assignment; error.details SHOULD include format_option_id when present, accepted_language_ranges, available_variant_locales, and placement identity when applicable. |
| `CREATIVE_MISSING_CLICK_URL` | correctable | A submitted creative that requires a destination URL does not provide one. Sellers SHOULD identify the missing buyer-visible field in error.field and MUST NOT expose downstream ad-server names or internal object identifiers in the buyer-facing message. |
| `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, brand-safety, or accessibility review. For deadline violations, see CREATIVE_DEADLINE_EXCEEDED. Accessibility failures SHOULD use structured details conforming to error-details/accessibility-violation.json. |
| `CREATIVE_REPRESENTATION_UNRESOLVED` | correctable | No representation in a CreativeRepresentationSet is compatible with the selected target capability or product format option. The resolver MUST retain the complete representation set and MUST include one `error.details.representation_rejections[]` entry per candidate, conforming to `error-details/creative-representation-unresolved.json`; silent seller guessing is forbidden. |
| `CREATIVE_REVISION_CONTENT_MISMATCH` | correctable | A sync_creatives item reused a revision_id for different canonical revision content under the same creative_id. Revision identity is scoped to the parent creative and immutable after first acceptance. Sellers MUST evaluate the buyer input before transcoding or normalization, MUST leave the prior creative state unchanged, and SHOULD return details conforming to error-details/creative-revision-content-mismatch.json. Distinct from IDEMPOTENCY_CONFLICT: idempotency_key protects one request replay window, while revision identity protects creative content across requests and retention. |
| `CREATIVE_SIZE_MISMATCH` | correctable | The submitted creative dimensions do not match any size accepted by the selected packages. Sellers SHOULD identify the offending creative in error.field and MAY include the submitted and accepted dimensions in buyer-safe error.details. |
| `CREATIVE_VALIDATION_FAILED_GENERIC` | correctable | The creative failed buyer-correctable validation, but the producer cannot classify the failure with a more specific standard code. Producers SHOULD prefer a specific creative code whenever one applies and MUST keep buyer-facing messages free of vendor identifiers, internal object names, internal IDs, and stack traces. |
| `CREATIVE_VALUE_NOT_ALLOWED` | correctable | A submitted text-asset value is not in the format's declared `allowed_values` list. Distinct from `CREATIVE_REJECTED` (generic creative-review failure) by being a closed-set constraint violation that the buyer can resolve mechanically without policy interpretation — the seller has published the complete list of acceptable values on the format, and any value outside that list is rejected by definition. The seller MUST set `error.field` to the offending asset's path within the manifest (e.g., `creatives[0].creative_manifest.assets[0].value` or the field name declared by the format) and SHOULD include the format's `allowed_values` array in `error.details.allowed_values` so the buyer agent can re-prompt its LLM with constrained sampling. |
| `CREDENTIAL_IN_ARGS` | terminal | The seller detected authentication material or caller-supplied trust material placed in request args (top-level, in `context`, in `ext`, or any other nested location in the task payload) instead of arriving on the relevant transport authentication or trust channel. This includes buyer-principal credentials that should arrive on the inbound transport (`Authorization: Bearer` per RFC 6750 §2 for HTTP, RFC 9421 signature headers for signed requests, MCP/A2A authentication framing per RFC 9728 §3), and evaluator-call credentials or JWK/JWKS/JWKS-URI trust material smuggled into evaluator-related payload fields instead of being established through the creative agent's outbound transport authentication to the evaluator. Distinct from `AUTH_MISSING` (no credentials presented on the transport channel) and `AUTH_INVALID` (credentials presented but rejected on the transport channel) and `PERMISSION_DENIED` (authenticated caller not authorized for the action). Distinct from the receiver-side credentials carried in `push_notification_config.authentication.credentials`, which configure the seller's webhook callback authentication and are not buyer-principal or evaluator-call credentials — those are an explicit carve-out and MUST NOT trigger this code. Sellers SHOULD reject credential-in-args under AdCP 3.1; the requirement upgrades to MUST 90 days after the 3.1 publication date. |
| `CURSOR_EXPIRED` | correctable | The list_account_changes cursor is no longer within the seller's retained account change window. The seller MUST NOT silently restart from the retention boundary. error.details SHOULD include available_since and MAY include a replacement starting-position hint, without disclosing inaccessible history. |
| `EVALUATOR_AGENT_NOT_ACCEPTED` | correctable | Buyer attached an evaluator agent pointer on `build_creative` — `evaluator.feature_agent.agent_url` or the `evaluator` agent-form `agent_url` — that does not match (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) any entry in the seller's `creative_policy.accepted_verifiers[].agent_url`. The producing agent does not call buyer-asserted endpoints outside its allowlist; this mirrors `PROVENANCE_VERIFIER_NOT_ACCEPTED` for the gate/rank evaluator path — the buyer represents which on-list agent it used, the seller is the agent-of-record and calls only allowlisted agents. `error.field` MUST point at the offending `agent_url` path; `error.details` SHOULD include a reference to the product whose `creative_policy.accepted_verifiers` the buyer should consult. |
| `FEED_FETCH_FAILED` | correctable | Platform could not fetch the catalog feed URL during sync_catalogs. |
| `FIELD_NOT_PERMITTED` | correctable | A request field is not in the caller's `field_scopes` allowlist for this task. Sellers declaring `field_scopes` on the account's `authorization` object MUST reject any request that sets a non-allowlisted field with this code. Distinct from `VALIDATION_ERROR` (schema/business-rule violation) - the field is valid, just not writable by this caller. `error.field` MUST identify the exact offending field path (e.g., `packages[0].budget`); when multiple fields are disallowed, sellers SHOULD return one error per field, or MAY enumerate them in `error.details.fields`. |
| `FORMAT_DECLARATION_DIVERGENT` | correctable | Non-fatal advisory raised when a product carries BOTH `format_ids` (v1) AND `format_options` (v2) and the two disagree (different canonical, different dimensions, different orientation) after projection. The producer's contract is that both shapes MUST refer to the same underlying declaration; divergence is a producer bug. Either side MAY emit this code: a SELLER may self-detect on emit (own producer bug; rare), or more commonly a consumer-SDK detects on consumption. SDKs MUST prefer `format_options` (the richer surface) when both are present and MUST surface the divergent product so it's observable rather than silently picked-one-and-dropped-other. Hard-failing the entire `get_products` response is discouraged — it punishes downstream buyers for the producer bug. **Surface placement (normative).** Same single-surface mandate as `FORMAT_PROJECTION_FAILED`: SDKs that detect this on consumption MUST augment the response's `errors[]` array with an entry carrying `source: "sdk"`, `sdk_id: "<package>@<version>"`, `code: "FORMAT_DECLARATION_DIVERGENT"`, and the field+details described below. Logger-only is insufficient; lint-output channels are NOT acceptable as the surface (the multi-hop agent network needs warnings to propagate across SDK boundaries via the wire response). `error.field` MUST point at the offending product; `error.details` SHOULD carry `{ product_id, format_ids, format_options_summary, divergence_reason }` so buyer SDKs can flag the producer for follow-up. **Multi-hop deduplication.** Each hop that detects the same divergence SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry's `sdk_id` identifies which earlier processor saw it first. |
| `FORMAT_DECLARATION_V1_AMBIGUOUS` | correctable | Non-fatal advisory raised when an SDK detects that a product's v2 declaration cannot be unambiguously projected back to a single v1 named format because the v1-canonical-mapping registry has only family-level structural entries for this canonical (no invertible `format_id_glob` literal). The family is known (e.g., 'this is a video_vast'); the specific v1 named format isn't pickable mechanically. Distinct from `FORMAT_PROJECTION_FAILED` (registry-coverage gap, correctable by adding a registry entry) — ambiguity is structural: the family is defined but a specific format can't be picked without seller assertion. Surface placement: same single-mandate as `FORMAT_PROJECTION_FAILED` and `FORMAT_DECLARATION_DIVERGENT` — SDKs MUST augment the response's `errors[]` array with an entry carrying `source: "sdk"`, `sdk_id`, `code: "FORMAT_DECLARATION_V1_AMBIGUOUS"`, `field` pointing at the offending declaration, and `error.details` SHOULD carry `{ format_kind, registry_matches: [<list of structural entries that matched at family level>], product_id }` so adopters can see why the inversion was ambiguous. **SDKs MUST NOT synthesize a v1_format_ref** in this case (or any other case). v1↔v2 explicit pairing is seller-asserted only — SDKs encountering family-only registry matches MUST treat the v2 declaration as v1-unreachable and surface this code rather than invent a plausible v1 format_id. The seller's path: author `v1_format_ref` on the v2 declaration to disambiguate (the authoritative pairing per `v1-canonical-mapping.json` resolution step 1), or accept that v1-only buyers won't see this product. |
| `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` | correctable | Non-fatal advisory raised when a v2 declaration carries `params.sizes[]` with N entries but only M v1_format_ref entries (M < N). The seller has asserted some v1 named formats but not enough to cover all declared sizes — v1-only buyers see partial coverage on the product. Emitted **alongside** the partial v1 emission (NOT in place of it): the product still appears on the v1 wire under the M sizes the seller covered; this code tells v1-aware downstream agents that N-M sizes were dropped from the projection. Surface placement: SDKs that detect on emission OR consumption MUST augment the response's `errors[]` with `source: "sdk"` (or `"producer"` if the seller self-detects on emit), `sdk_id`, `code: "FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE"`, `field` pointing at the offending declaration, and `error.details` SHOULD carry `{ product_id, declared_sizes: [{w,h}, …], covered_sizes: [{w,h}, …], dropped_sizes: [{w,h}, …] }` so buyer agents see which sizes were lost. **SDKs MAY (non-normative) fan out automatically** by catalog lookup — for each entry in `sizes[]` lacking a corresponding `v1_format_ref`, the SDK consults the AAO catalog for the per-size v1 named format (e.g., for `{width: 728, height: 90}` look up `display_728x90_image`) and emits it under `format_ids[]`. This is opt-in (requires catalog access); when SDKs fan out, they SHOULD still emit this code as a transparency advisory so downstream consumers know the v1 emit was synthesized rather than seller-asserted. Recovery: warning — non-fatal, no retry. Seller fix: add `v1_format_ref[]` entries for the missing sizes. |
| `FORMAT_NOT_SUPPORTED` | correctable | A requested creative operation route is not supported by this creative agent. On the canonical 3.2 path, returned when build_creative.target_capability_id(s), preview_creative.target_capability_id, or validate_input targets[] kind capability does not match an advertised creative.supported_formats[].capability_id carrying the requested operation. Also returned when preview renderer inference has zero or multiple compatible matches. Sellers SHOULD attribute the error to the selector field and MAY include supported capability IDs in error.details.supported_capability_ids when safe. Deprecated target_format_id(s) and preview format_id retain legacy named-format error attribution during the 3.x compatibility window. |
| `FORMAT_OPTION_UNRESOLVED` | correctable | Non-fatal advisory raised when a placement in `adagents.json` (or any consumer of `placement-definition.json`) carries `format_options[].format_option_id` referencing a `format_option_id` that does NOT exist in the file's top-level `formats[]`. The reference is broken — the publisher's catalog claims the placement accepts a format option that isn't declared. **Resolution scope is same-file only.** Cross-file `format_option_id` lookup is not supported by design (closes off format_option_id squatting across publisher boundaries — a malicious file cannot reference another publisher's format_option_id and claim its narrowing). Buyer SDKs MUST fail closed for the placement (drop the format from the placement's accepted format set) and MUST surface this code rather than silently dropping or guessing what the publisher meant. Surface placement: same single-mandate as the other FORMAT_* codes — SDKs that detect on consumption MUST augment the response's `errors[]` with `source: "sdk"`, `sdk_id`, `code: "FORMAT_OPTION_UNRESOLVED"`, `field` pointing at the offending placement (e.g., `placements[2].format_options[1].format_option_id`), and `error.details` SHOULD carry `{ placement_id, format_option_id, declared_format_options: [<list of format_option_ids actually in formats[]>] }` so the publisher can fix. |
| `FORMAT_PROJECTION_FAILED` | correctable | Non-fatal advisory raised when a legacy named format on a product cannot be projected to a canonical-formats `ProductFormatDeclaration` via the resolution order in `v1-canonical-mapping.json` (explicit `canonical` field → format_id_glob → structural match → fail-closed). The product is still valid on the legacy named-format path; only the 3.1+ `format_options` projection failed. Primarily a **consumer-SDK concern** — the seller didn't fail; the consumer-side SDK couldn't project on their behalf. `error.field` MUST point at the offending product (e.g., `products[3].format_ids[0]`); `error.details` SHOULD carry `{ format_id, product_id, resolution_failure: "no_explicit_canonical" | "no_registry_match" | "no_structural_match" }` so buyer SDKs can route remediation (suggest the seller add an explicit `canonical` field, or file a registry PR). **Surface placement (normative).** SDKs that detect this on consumption MUST augment the response's `errors[]` array with an entry carrying `source: "sdk"`, `sdk_id: "<package>@<version>"`, `code: "FORMAT_PROJECTION_FAILED"`, and the field+details described above. This is the single mandated surface — logger-only is insufficient and a separate lint-output channel is NOT acceptable (AdCP is a multi-hop agent network; warnings need to propagate across hops or each hop has to re-detect locally). Sellers MAY emit this code on their own response when they self-detect a non-projectable format on emit; producer-emitted entries omit `source` (or set `source: "producer"`). The response stays 200/success regardless of who emits; this is non-fatal. **Multi-hop deduplication.** Each hop that detects the same condition SHOULD deduplicate by `(code, field)` rather than re-emit. The existing entry's `sdk_id` identifies which earlier processor saw it first; downstream SDKs SHOULD NOT add a second entry for the same `(code, field)` pair unless they have materially different `error.details` (e.g., a different `resolution_failure` reason from a different registry version). See canonical-formats.mdx 'Dual emission and v2↔v1 projection' for the full rules. |
| `FORMAT_SHAPE_PROMOTED` | correctable | Non-fatal deprecation advisory raised when a 3.2-aware SDK encounters `format_kind: custom` with a `format_shape` that has been promoted to a first-class canonical. SDKs MUST preserve the declaration during its transition window and SHOULD augment the containing response's `errors[]` with `source: sdk`, `sdk_id`, this code, and `details: { format_shape, promoted_to, promotion_release, transition_end }`. Producers that self-detect their own legacy declaration MAY emit the same advisory with `source: producer`. Recovery is seller-side: dual-emit during the published transition window, migrate consumers, then replace the custom declaration with the promoted canonical. |
| `GOVERNANCE_AGENT_NOT_ACCEPTED` | correctable | The governance agent proposed in `sync_governance` does not satisfy the seller's authoritative per-account acceptance criteria. The failed binding MUST NOT be persisted or contacted, and credentials supplied for it MUST NOT be echoed in responses or logs. `error.details` SHOULD conform to `error-details/governance-agent-not-accepted.json`. Distinct from `GOVERNANCE_UNAVAILABLE`, which means a verification criterion or registry could not be resolved and is retryable. |
| `GOVERNANCE_DENIED` | correctable | A registered governance agent denied the transaction. Sellers MUST place the denial in the operation's structured rejection arm when one exists (e.g., `acquire_rights` → `AcquireRightsRejected`, or an `approval_webhook` delivery → `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 operation. 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. Operation or webhook payload 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 operation completed successfully and produced a structured response. 2. Operation 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 operation or webhook payload 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 reconciliation BEFORE minting a new key — minting a new key in that situation is exactly how double-creation happens. |
| `IDEMPOTENCY_IN_FLIGHT` | transient | A prior request with the same `idempotency_key` is still being processed and has not yet produced a cached response. The second request arrived before the first completed. Sellers MAY return this code instead of blocking the second caller until the first finishes — useful when the first call invokes a slow downstream system (SSP, ad server, payment provider). Distinct from IDEMPOTENCY_CONFLICT (different canonical payload — a client bug) and from CONFLICT (concurrent modification of a different resource) — IDEMPOTENCY_IN_FLIGHT is the seller telling the buyer 'your retry was correct but your previous attempt is still running, come back shortly.' Sellers SHOULD populate top-level `error.retry_after` with an integer-second wait hint based on the first request's elapsed time and expected completion. Buyers MUST treat this as transient and MUST NOT mint a fresh `idempotency_key` — minting a new key turns a safe retry into a double-execution race. |
| `INVALID_FEED_FORMAT` | correctable | Catalog feed content does not match the declared feed_format. |
| `INVALID_PRICING_OPTION` | correctable | A `pricing_option_id` referenced in the request does not exist on the target account or product. Returned per-record in `report_usage` responses and at the request level for `create_media_buy` when the submitted pricing option cannot be resolved. `error.field` SHOULD point at the offending record path (e.g., `usage[1].pricing_option_id` or `packages[0].pricing_option_id`). Distinct from `PRODUCT_NOT_FOUND` (the product itself is unknown) by being narrowly about a pricing option within a known product or account. |
| `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). |
| `INVALID_USAGE_DATA` | correctable | A usage record in `report_usage` has missing or invalid fields — required fields absent, values out of range, or type mismatches. Returned per-record in the `report_usage` response `errors[]` array. `error.field` SHOULD point at the offending field path (e.g., `usage[0].vendor_cost`, `usage[0].currency`). Distinct from `INVALID_REQUEST` (top-level request malformed) by being scoped to individual usage records within an otherwise well-formed request. |
| `IO_REQUIRED` | correctable | The committed proposal requires a signed insertion order but no io_acceptance was provided. |
| `ITEM_VALIDATION_FAILED` | correctable | One or more catalog items failed schema validation during sync_catalogs. |
| `MACRO_RESOLUTION_FAILED` | correctable | One or more declared creative macro tokens cannot be resolved or safely preserved under the selected product and seller capability intersection. Sellers MUST include per-token `error.details.macro_resolution_results`, conforming to `error-details/macro-resolution-failed.json`; unknown or ambiguous tokens remain byte-preserved and MUST NOT receive guessed values. |
| `MEDIA_BUY_NOT_FOUND` | correctable | Referenced media buy does not exist or is not accessible to the requesting agent. |
| `MULTI_FINALIZE_UNSUPPORTED` | correctable | Returned by sellers that cannot guarantee atomic commit across multiple proposals in a single finalize batch. Two call sites where this applies: (1) a `get_products` call with multiple `action: 'finalize'` entries in `refine[]` targeting different `proposal_id` values; (2) a `refine_proposals` call with multiple `action: 'finalize'` entries in `refinements[]` targeting different `proposal_id` values. The buyer's intent — atomic multi-proposal finalize — is structurally well-formed and per spec atomic on both surfaces, but this seller's downstream stack cannot satisfy the atomicity guarantee (e.g., the proposals route to two different ad servers with no 2PC). More specific than `INVALID_REQUEST` so buyers can distinguish 'this seller doesn't support multi-finalize' from 'the request itself is malformed'. See [refinement guide § Finalize is exclusive](/docs/media-buy/product-discovery/refinement#finalize-is-exclusive-within-refine). |
| `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. |
| `PAYMENT_TERMS_NOT_SUPPORTED` | correctable | The seller does not accept the requested `payment_terms` value for this account. Payment terms are never silently remapped — sellers either accept or reject. Distinct from `BILLING_NOT_SUPPORTED` (the `billing` enum) by being narrowly about the `payment_terms` enum on the same account. |
| `PERMISSION_DENIED` | correctable | The authenticated caller is not authorized for the requested action, 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. Seller content or advertising policy denials use `POLICY_VIOLATION`; media-buy change-right and current-availability denials use `ACTION_NOT_ALLOWED`. Distinct from `AUTH_MISSING` (no credentials presented), `AUTH_INVALID` (credentials presented but rejected), `GOVERNANCE_DENIED` (governance agent denied), `AGENT_SUSPENDED` (agent's relationship temporarily paused), and `AGENT_BLOCKED` (agent's relationship permanently denied). When the gate that fired is specifically a non-status per-agent provisioning constraint — e.g., the agent is provisioned for sandbox traffic only and the request was against a non-sandbox account — `error.details` SHOULD conform to `error-details/agent-permission-denied.json` (`scope: "agent"` plus `reason: "sandbox_only"`) so callers can dispatch without parsing prose. Sellers MUST emit `scope: "agent"` only when buyer-agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; in all other cases (including bearer credentials not mapped to a specific agent record) sellers MUST return `PERMISSION_DENIED` and MUST omit `error.details.scope` — emitting the per-agent scope without established identity is a cross-tenant onboarding oracle, and the omit MUST be enforced across every observable channel (response shape, HTTP/A2A/MCP status, headers, side effects, observability, latency parity) per the channel-coverage rules in error-handling.mdx Per-Agent Authorization Gate, mirroring the `*_NOT_FOUND` uniform-response rule and `BILLING_NOT_PERMITTED_FOR_AGENT`. The `suspended` and `blocked` per-agent states are NOT carried on this code — sellers MUST emit `AGENT_SUSPENDED` / `AGENT_BLOCKED` instead, each of which is its own discriminator. |
| `PIXEL_TRACKER_LOSSY_DOWNGRADE` | correctable | Non-fatal advisory raised when a 3.1 buyer SDK downgrades a `pixel_tracker` asset to the v1 `{asset_type: url, url_type: tracker_pixel}` shape for a 3.0.x seller that doesn't recognize the new asset type. The URL is still emitted on the wire and the seller will fire it as a tracker pixel; what's lost is the event/method discrimination. Downgrade rules (normative):
- `event: impression` + `method: img` → no loss; emit as `{asset_type: url, url_type: tracker_pixel, url, asset_id: impression_tracker}`
- `event: viewable_mrc_50` / `viewable_mrc_100` / `viewable_video_50` / `audible_video_complete` → emit with `asset_id: viewability_tracker`; advisory `lost_event: <variant>` (specific viewability variant collapses to a single v1 slot)
- `event: click` → emit with `asset_id: click_tracker`; no meaningful loss
- `event: custom, custom_event_name: X` → emit with `asset_id: impression_tracker` (default tracker_pixel fires on impression); advisory `lost_event: "custom"`, `lost_custom_event_name: X` (custom event timing collapses to impression timing)
- `method: js` → emit unchanged shape (url, url_type:tracker_pixel); advisory `lost_method: "js"` (v1 seller will fire as HTTP GET; the URL is hit and any counter-based measurement increments, but the response body won't execute as JS — measurement that depends on JS execution, e.g., OMID-style verification, viewability observers, cross-domain cookie setters, won't work. Simple counter pixels still work.) Surface: SDK that performs the downgrade MUST augment the response's `errors[]` with `source: "sdk"`, `sdk_id`, `code: "PIXEL_TRACKER_LOSSY_DOWNGRADE"`, `field` pointing at the affected manifest asset path, and `error.details` SHOULD carry `{ asset_id, original_event, original_method, original_custom_event_name (if present), downgrade_target: "url+tracker_pixel", lost_fields: [<list>] }`. One advisory per downgraded asset; SDKs SHOULD NOT collapse multiple downgrades into a single advisory entry — per-asset details let the buyer's measurement-plan owner decide whether each loss is tolerable. Recovery: warning — non-fatal, no retry. Buyer-side decision: accept the loss (most simple counter pixels survive), or fail the buy and route to a 3.1-capable seller. Seller-side fix: upgrade to 3.1 and accept `pixel_tracker` natively. |
| `PIXEL_TRACKER_UPGRADE_INFERRED` | correctable | Non-fatal advisory raised when a 3.1 buyer SDK upgrades a v1 `{asset_type: url, url_type: tracker_pixel}` to a `pixel_tracker` asset by INFERRING the event and method from the v1 asset_id and conventional defaults. The inference is structural — the SDK doesn't have explicit event/method values, only the v1 asset_id hint and `url_type: tracker_pixel` (which implies `method: img` by default). Inference rules (normative):
- `asset_id: impression_tracker` → `event: impression, method: img`
- `asset_id: viewability_tracker` → `event: viewable_mrc_50, method: img` (50% is the most common default; specific viewability variant cannot be recovered from v1 shape)
- `asset_id: click_tracker` → `event: click, method: img`
- `asset_id: <other>` → `event: custom, custom_event_name: <original asset_id>, method: img` Surface: SDK MUST augment the response's `errors[]` with `code: "PIXEL_TRACKER_UPGRADE_INFERRED"`, `field` pointing at the upgraded asset path, and `error.details` SHOULD carry `{ asset_id, inferred_event, inferred_method, inference_basis: "asset_id_convention" | "default" }`. Buyer agents reading the response can re-prompt the seller for explicit values if precise measurement matters. Recovery: warning — non-fatal, no retry. Seller-side: upgrade emit path to ship pixel_tracker shape directly when 3.1-capable; until then, conventional asset_id values give the SDK enough signal to upgrade without losing critical semantics. |
| `PLACE_TARGET_UNAVAILABLE` | correctable | A place identifier previously accepted and pinned on a package can no longer be executed. This is a nonfatal resource-state error returned in get_media_buys.errors[] alongside the affected buy. error.field MUST point to the exact media_buys[N].packages[M].targeting_overlay.geo_places[_exclude][A].values[V] response path. error.details MUST include media_buy_id, package_id, system, system_version, country, place_type, and value. The seller MUST preserve and echo the pinned target rather than silently changing geography. |
| `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. |
| `PRIVATE_FIELD_IN_PUBLIC_PLACEMENT` | correctable | Fatal producer-side error raised when a public placement object (`Product.placements[]` in `get_products` or `placements[]` in adagents.json) exposes seller-private operational fields such as `visibility`, `source`, `origin`, or `delivery_mappings`. This is a private-data leak, not an ordinary syntactic mismatch. Consumers that detect it MUST fail closed for that placement and surface this code so monitoring can alarm on the leak specifically instead of burying it under generic schema validation. `error.field` SHOULD point at the offending placement path and `error.details` SHOULD carry `{ placement_id, leaked_fields: [<field names>] }` without echoing private field values. |
| `PRODUCT_EXPIRED` | correctable | The seller still recognizes one or more configured product IDs as issued to the authenticated account and referenced discovery/refinement lineage, and they have passed their expires_at timestamps. Sellers are not required to retain expiry tombstones indefinitely; an ID that is no longer resolvable, or is inaccessible to this caller because it belongs to another account or lineage, uses PRODUCT_NOT_FOUND. This distinction MUST NOT become a cross-tenant existence oracle. |
| `PRODUCT_NOT_FOUND` | correctable | One or more referenced product IDs are unknown or are not resolvable within the authenticated account and configured-offer lineage. A caller-authorized configured ID that is still recognized as expired uses PRODUCT_EXPIRED; once no expiry tombstone remains, or whenever the ID belongs to another account or lineage, PRODUCT_NOT_FOUND applies. Sellers MUST NOT reveal cross-tenant product existence through error choice. |
| `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. For a committed proposal, the inventory hold has lapsed. |
| `PROPOSAL_NOT_COMMITTED` | correctable | The referenced proposal has proposal_status 'draft' and cannot be accepted into a media buy. |
| `PROPOSAL_NOT_FOUND` | correctable | The referenced proposal_id is not recognized by the seller — it belongs to a different tenant, was never issued, or was evicted from the seller's session cache before consumption. Distinct from `PROPOSAL_EXPIRED` (a known proposal whose `expires_at` window has passed) and `PROPOSAL_NOT_COMMITTED` (a known proposal still in `draft`). |
| `PROVENANCE_CLAIM_CONTRADICTED` | correctable | Seller invoked a governance agent from `creative_policy.accepted_verifiers` via `get_creative_features` and the verifier's result contradicts the buyer's provenance claim - e.g., buyer claims `digital_source_type: digital_capture` but the AI-detection feature returns `ai_generated: true` above the seller's confidence threshold. Distinct from the `PROVENANCE_*_MISSING` family (structural absence) by being an active refutation. `error.details` SHOULD be limited to the audit-safe allowlist `{ agent_url, feature_id, claimed_value, observed_value, confidence }`; sellers MUST NOT forward arbitrary verifier extension fields, `detail_url`, or any verifier response shape that may carry cross-tenant or PII data. When the seller calls a different on-list agent than the buyer nominated (the seller is the verifier-of-record), `error.details.agent_url` is the agent the seller actually called and `error.details.substituted_for` SHOULD carry the buyer's nominated `agent_url` so the buyer can reconcile. |
| `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING` | correctable | Seller's `creative_policy.provenance_requirements.require_digital_source_type` is true and the submitted creative's resolved provenance (after inheritance) has no `digital_source_type` value, or has it set to null. Distinct from `PROVENANCE_REQUIRED` (no provenance object at all) - provenance is present, just missing this specific field. `error.field` MUST point at the resolved provenance path that was inspected (e.g., `creatives[0].creative_manifest.provenance.digital_source_type`). |
| `PROVENANCE_DISCLOSURE_MISSING` | correctable | Seller's `creative_policy.provenance_requirements.require_disclosure_metadata` is true and the submitted creative's resolved provenance has no `disclosure.required` boolean, or `disclosure.required` is true with no `disclosure.jurisdictions` entries. `error.field` MUST point at `provenance.disclosure` (e.g., `creatives[0].creative_manifest.provenance.disclosure`). |
| `PROVENANCE_EMBEDDED_MISSING` | correctable | Seller's `creative_policy.provenance_requirements.require_embedded_provenance` is true and the submitted creative's resolved provenance has no `embedded_provenance` array, or has it as an empty array. Used in pipelines where sidecar `c2pa.manifest_url` is stripped by intermediaries and the seller requires content-stream-resilient provenance. `error.field` MUST point at `provenance.embedded_provenance` on the resolved manifest. |
| `PROVENANCE_REQUIRED` | correctable | Seller's `creative_policy.provenance_required` is true and the submitted creative has no `provenance` object on the manifest, on the creative-asset, or on any individual asset. Distinct from `CREATIVE_REJECTED` (generic creative-review failure) by being narrowly about provenance presence. `error.field` MUST point at the path where provenance was expected (e.g., `creatives[0].creative_manifest`). |
| `PROVENANCE_SYNTHETIC_DEPICTION_MISSING` | correctable | Seller's `creative_policy.provenance_requirements.require_synthetic_depiction` is true and the submitted creative's resolved provenance (after inheritance) has no `synthetic_depiction` boolean. Both `true` and `false` satisfy the requirement; absence means unassessed. Distinct from `PROVENANCE_REQUIRED` (no provenance object at all) and from `PROVENANCE_CLAIM_CONTRADICTED` (an independent verifier actively refuted a declared value). `error.field` MUST point at the resolved `provenance.synthetic_depiction` path. The declaration does not establish consent, legality, or verification. |
| `PROVENANCE_VERIFIER_NOT_ACCEPTED` | correctable | Buyer attached a `verify_agent.agent_url` on `embedded_provenance[]` or `watermarks[]` that does not match (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) any entry in the seller's `creative_policy.accepted_verifiers[].agent_url`. The seller does not call buyer-asserted endpoints outside its allowlist; this is the cross-check that closes the buyer-controlled-URL trust gap. `error.field` MUST point at the offending `verify_agent.agent_url` path; `error.details` SHOULD include a reference to the product whose `creative_policy.accepted_verifiers` the buyer should consult (the buyer already has this from `get_products`). |
| `RATE_LIMITED` | transient | Request rate exceeded. Sellers SHOULD populate top-level `error.retry_after` with the integer number of seconds to wait. |
| `READ_ONLY_SCOPE` | correctable | The caller's scope is read-only; the invoked task would mutate state and was rejected. Distinct from `SCOPE_INSUFFICIENT` (task not in scope at all) — the task is in some scopes this seller supports, just not this caller's. |
| `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 | A control_media_buy request, or the 3.x update_media_buy facade, would exceed the accepted commercial envelope. The seller is declining the requested shape at the current terms. 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. AdCP 3.2 callers refine the MediaBuy's accepted_proposal_id to obtain a typed amendment; legacy 3.1 callers adjust the update, rediscover terms, or create a separate buy. |
| `SCOPE_INSUFFICIENT` | correctable | The authenticated caller is not authorized for the invoked task — the task is not in the caller's `allowed_tasks` for this account (discoverable via the `authorization` object on sync_accounts / list_accounts responses). Distinct from `PERMISSION_DENIED` (generic authz failure, often credential-shaped) by being narrowly about task-level scope. Sellers SHOULD populate `error.details.introspection_hint` pointing at where the caller can re-read its scope (strawman: `{ task: 'list_accounts', account: {...} }`). |
| `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_ref not accessible to the calling account — never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration. |
| `SIGNAL_TARGETING_INCOMPATIBLE` | correctable | A creative carrying a signal_condition (from build_creative signal_conditions fan-out, #5240) was assigned to a package whose signal targeting is incompatible — e.g. a sun creative routed to a rain-targeted package. The trafficking-compatibility invariant: a creative built FOR one signal condition MUST NOT serve into a package targeting an incompatible condition. Enforced reject-at-trafficking on the sales side (create_media_buy / sync_creatives), NOT at build_creative (per #5280, signal pointers are advisory at the build layer; enforcement lives at the trafficking boundary). Compatibility is matched on shared signal_ref identity: when both sides carry signal_agent_segment_id, compare the opaque handle exactly; when both carry only categorical {signal_id,value}, compare signal_ref + value-set semantics; equal categorical labels from DIFFERENT providers are NOT compatible absent an explicit equivalence mechanism; when one side has a segment handle and the other only a categorical value, the seller MAY accept only if it can resolve both to the same provider-issued segment, else reject/warn. For value_type:numeric the comparison is range-overlap (WG-open: range-overlap vs exact-match — see RFC #5240 open decisions). error.field SHOULD point at the offending assignment path (e.g. packages[N].creative_assignments[M] or creatives[N]); error.details SHOULD carry the creative's signal_condition and the package's incompatible signal targeting so the buyer can re-route. Distinct from SIGNAL_NOT_FOUND (signal unknown/inaccessible) by being a compatibility mismatch between a known creative condition and a known package condition. |
| `SIGNED_RESPONSE_ENVELOPE_EXPIRED` | transient | The `signed_response.payload.exp` timestamp is at or past the current time after applying the verifier's clock-skew tolerance. The signed envelope was valid when issued but the verification window has closed. Raised by verifiers consuming a designated-task signed response (`verify_brand_claim` / `verify_brand_claims`) during step 7 of the [response-signing verifier checklist](/docs/building/by-layer/L1/security#verifier-checklist-responses). Online verifiers MUST reject expired envelopes; audit verifiers MAY verify after `exp` as historical evidence that the brand-agent signed the payload during the stated `iat`/`exp` window, but MUST NOT treat the result as current truth. Distinct from `STALE_RESPONSE` (cache-staleness advisory on a populated success payload) by being a cryptographic freshness failure on a signed response envelope. |
| `SIGNED_RESPONSE_REQUEST_HASH_MISMATCH` | correctable | The `signed_response.payload.request_hash` does not match the verifier's recomputed SHA-256 hash of the canonical request-binding object `{ task, brand_domain, agent_url, caller_identity, request }` for the actual request sent. Raised during step 8 of the [response-signing verifier checklist](/docs/building/by-layer/L1/security#verifier-checklist-responses). This indicates either a replay of a signed response from a different request, a JCS request-binding canonicalization divergence between signer and verifier, or payload tampering. Verifiers MUST reject the envelope when the recomputed hash does not byte-match the signed `request_hash`. `error.details` SHOULD carry `expected_request_hash` (the verifier's recomputed value) when safe to disclose — the hash binds the response to the exact request that produced it, and divergence is the signal that the binding has been broken. |
| `SIGNED_RESPONSE_TENANT_MISMATCH` | correctable | The `signed_response.payload.brand_domain` does not match the verifier's resolved or expected brand tenant for this verification call. Raised during step 9 of the [response-signing verifier checklist](/docs/building/by-layer/L1/security#verifier-checklist-responses). The responding brand agent derives `brand_domain` from server-side tenant resolution, not from caller-supplied request fields (per the response-signing profile in security.mdx), so a mismatch indicates cross-tenant replay, a multi-brand agent routing error, or a mismatch between the verifier's tenant resolution and the responder's. Distinct from `SIGNED_RESPONSE_REQUEST_HASH_MISMATCH` (request-binding divergence) by being narrowly about the tenant identity — `brand_domain` is a field inside the signed payload that the responder derives independently, not an echo of any caller-supplied value. |
| `SOURCE_ACCESS_FAILED` | correctable | Seller could not read an external audience source on sync_audiences (experimental, media_buy.audience_activation). Per-audience: surfaces as action: failed with this code in the audience's errors array. The seller cannot reliably distinguish a revoked grant from an expired one from a transient vendor outage — all three observables are a failed read — so error.field distinguishes what the buyer can act on: 'source.locator' or 'source.segment_ref' means the reference does not resolve (changed request needed); 'source' means access denied (establish or re-grant access to the seller's declared consumer identity, then retry); absence of error.field indicates a vendor-side failure (retry with backoff, no buyer action). On an already-ready audience a failed re-read MUST NOT change audience status — membership stays frozen at the last successful read and source.access_status reports 'unavailable'. |
| `STALE_RESPONSE` | transient | Non-fatal advisory raised when the seller's live fetch to an upstream or sub-agent failed (timeout, connection error, downstream 5xx) and the response payload was satisfied from a cached prior result that is past the seller's freshness target for this surface. Emitted **alongside** a populated success payload — the caller's request still completes from a usable cache hit; this code tells downstream consumers that the data is older than the seller would normally serve. Distinct from `SERVICE_UNAVAILABLE` (seller's own service is down, no payload — transient, retry-with-backoff) by signalling **graceful degradation**: the seller's own service is fine, but one of its dependencies is currently unreachable and the seller chose to honor the request from cache rather than return empty. Sellers MUST emit `STALE_RESPONSE` ONLY when the response payload is non-empty AND derived from a cache entry whose `cache_age_seconds` exceeds the surface's freshness target. When no cached entry exists (or the cache hit is within freshness target), sellers MUST NOT emit this code — return the empty-or-fresh response with whatever upstream-failure code applies (e.g., `SERVICE_UNAVAILABLE`). **Wire placement (normative).** Transport-level success markers stay flipped to success (HTTP 200, MCP `isError: false`, A2A `succeeded`) — the task ran successfully and produced a response, even if from cache. The advisory rides in `errors[]` on the payload and MUST NOT be promoted to `adcp_error` on the envelope (envelope-level errors are reserved for the empty-payload failure case per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`). `error.field` SHOULD point at the affected payload path (e.g., `formats` for `list_creative_formats`, `products` for `get_products`). `error.details` SHOULD conform to `error-details/stale-response.json` — `served_from_cache` (required, always `true`), `cache_age_seconds` (required), and optionally `freshness_target_seconds`, `upstream` (the dependency that failed), and `original_error` (the underlying failure code/message). **Multiple stale upstreams.** When N sub-agents are stale (e.g., a `list_creative_formats` registry aggregating from multiple creative agents), the seller SHOULD emit **one `STALE_RESPONSE` entry per affected upstream** rather than aggregating — the per-upstream shape mirrors the existing precedent set by `PIXEL_TRACKER_LOSSY_DOWNGRADE` (one advisory per downgraded asset) and lets buyer agents reason about which sub-population of the payload is stale. Each entry's `error.field` SHOULD narrow to the affected slice (e.g., `formats` for formats sourced from the stale upstream). |
| `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. |
| `UNPRICEABLE_OUTPUT` | correctable | A creative transformer build targets an output capability that no pricing option covers — no transformer.pricing_options entry has a matching applies_to_output_capability_ids and none is unscoped. The build is rejected rather than billed at a guessed rate. |
| `UNSUPPORTED_FEATURE` | correctable | A requested feature or field is not supported by this seller. When rejecting a `refine_proposals` request that uses a typed dimension omitted from an explicit `proposal_refinement.supported_dimensions` declaration, the error details SHOULD follow `error-details/unsupported-refinement-dimension.json` — `unsupported_dimension` names the offending dimension and `supported_dimensions` echoes the seller's declaration so the buyer can remove or translate the field without another capability round trip. |
| `UNSUPPORTED_GRANULARITY` | correctable | The requested `time_granularity` on `get_media_buy_delivery` is not in the product's declared `reporting_capabilities.windowed_pull_granularities`. Distinct from `UNSUPPORTED_FEATURE` (generic capability mismatch) by being narrowly about reporting-window granularity — the buyer asked for hourly pull-recovery on a product that only honors daily pulls, for example. Sellers MAY echo the declared set in `error.details.supported_granularities` when the caller is authorized to read the product's reporting capabilities — the same set is already available via `get_adcp_capabilities`, so the echo is a convenience, not load-bearing. Sellers MUST NOT echo a granularity set the caller could not otherwise read (per-product capability views vary by buyer entitlement). The `error.field` SHOULD point at `time_granularity`. Buyers that need higher-frequency recovery than the seller's pull set supports MUST rely on the webhook channel as primary at that frequency — the seller's `available_reporting_frequencies` may legitimately exceed `windowed_pull_granularities` (e.g., a stream-tap webhook on Kafka with warehouse pulls only at daily). |
| `UNSUPPORTED_PROVISIONING` | correctable | The seller does not support the `sync_accounts` mode the entry requested. Returned per-entry in the `sync_accounts` response when (a) an entry keyed by the natural-key trio (`brand` + `operator` + `billing`) is sent to a seller that does not provision accounts via AdCP — typical for account-id namespaces where accounts are pre-provisioned out of band or discovered via `list_accounts`; or (b) an entry keyed by `account` (AccountRef) is sent to a seller that has not implemented the settings-update mode. Distinct from `UNSUPPORTED_FEATURE` (generic capability mismatch) by being narrowly about which of the two `sync_accounts` modes the seller implements. The two modes are mutually exclusive per-entry — the seller MUST NOT silently downgrade or upgrade between them. Sellers MAY declare which modes they implement via `get_adcp_capabilities` (forward-looking — capability declaration shape is open). The `error.field` SHOULD point at the entry index where the unsupported shape was found. |
| `VALIDATION_ERROR` | correctable | Request contains invalid field values or violates business rules beyond schema validation. |
| `VAST_PARSE_FAILED` | correctable | A submitted `vast` asset failed document-level validation: the inline `content` (or the document fetched from `url`) is not well-formed XML, has no `<VAST>` root element, contains no `<Ad>` element, or an `<InLine>` linear creative carries no `<MediaFile>`. Returned by sellers that declare `creative_specs.vast_validation` of `document` or `wrapper`; sellers at the default `structural` level do not inspect the VAST document and MUST NOT return this code. Distinct from `VALIDATION_ERROR` (manifest-level format validation): the manifest was structurally valid, the VAST document inside it was not. Sellers SHOULD set `error.field` to the offending asset path and SHOULD populate `error.details.reason` with one of `not_xml`, `no_vast_root`, `no_ad`, `no_media_file`. Unresolved ad-server macros in URLs (`[MACRO]`, `${MACRO}`, `{UNIVERSAL_MACRO}`) are opaque tokens, not parse failures. |
| `VAST_VERSION_MISMATCH` | correctable | A submitted `vast` asset is version-incompatible when its exact `vast_version` is absent from the intersection of the selected product format option's `params.vast_versions` and the seller's `execution.creative_specs.vast_versions`, or when an inspected document violates its applicable VAST version rule. Modern `error.details` MUST conform to `error-details/vast-version-mismatch.json`: acceptance failures carry `mismatch_reason: asset_outside_acceptance`, `asset_vast_version`, `product_vast_versions`, and `seller_vast_versions`, plus `format_option_ref` only when the selected option is addressable; inspected-document failures carry `mismatch_reason: document_version_mismatch`, `asset_vast_version`, `observed_document_vast_version`, and `document_role`. Only the submitted document is compared for equality with the asset declaration; wrapper and terminal documents are checked against the acceptance intersection. The deprecated `supported_versions` field remains accepted for older 3.x peers. Distinct from `VERSION_UNSUPPORTED`, which concerns AdCP protocol negotiation. |
| `VAST_WRAPPER_DEPTH_EXCEEDED` | correctable | Resolving a `vast` asset's wrapper chain failed: the chain exceeded the format's declared `max_wrapper_depth`, revisited a `VASTAdTagURI` already seen in the chain (a loop), or a hop did not resolve within the seller's per-hop timeout. Returned by sellers that declare `creative_specs.vast_validation: "wrapper"`. Sellers SHOULD set `error.field` to the offending asset path and SHOULD populate `error.details.reason` with one of `depth`, `loop`, `timeout`, plus `error.details.depth` with the depth reached. |
| `VERSION_UNSUPPORTED` | correctable | The declared adcp_version (release-precision) or adcp_major_version (deprecated) is not supported by this seller. The error details SHOULD follow `error-details/version-unsupported.json` — `supported_versions` (release-precision strings) is authoritative for retry; `supported_majors` is deprecated. |

Unknown codes: fall back to the HTTP status code (4xx = correctable, 5xx = transient).

## Test Scenarios

Run compliance tests with `adcp test <agent> <scenario>`. 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 the raw-wire `mergeSeedProductLegacy` migration counterpart, `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<string, unknown>` 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`, including multi-tenant principal resolution.

By default the runner requests `attestation_mode: "raw"`, so returned calls include the redacted `payload` plus `payload_length`. For identifier-only checks it requests `attestation_mode: "digest"` with `identifier_value_digests`; the recorder can then omit raw payloads and return `identifier_match_proofs` showing which hashed storyboard values were observed. Storyboards that require payload introspection can declare `attestation_mode_required: "raw"`; if a controller only returns digest attestations, those payload assertions grade `not_applicable` rather than inspecting unavailable bodies.

## 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<T>` | Return type of every tool call (status + data/error/adcpError/correlationId/deferred/submitted; metadata includes seller-served `adcpVersion`) |
| `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 |
| `ReportingConsumerStatus` | Consumer acknowledgement for one config/report/period; see the four-state evidence matrix below |
| `EstablishedProposalStore` | Durable 3.0/3.1 proposal snapshots, atomic mutation fences, seven-day completion proofs, pruning, and submitted-task reconciliation |
| `WebhooksConfig.tenantScope` | Explicit trusted webhook namespace for a genuinely single-tenant server; multi-tenant servers derive scope per request |
| `PostgresTaskSettlementCoordinator` | Atomically commits a push task terminal state and PostgreSQL recovery-outbox checkpoint for different-process workers |
| `PostgresTaskSettlementIntentQueue` | Commits an exact terminal intent with application state, then recovers idempotent SDK task settlement after a crash |
| `PostgresWebhookRuntime` | Opinionated PostgreSQL webhook emitter, ready-to-wire server config, durable stores, migrations, probes, and bounded recovery |

`ReportingConsumerStatus` always carries `reporting_status_id`, delivery configuration identity, report definition, half-open period, `consumer_status`, and `status_as_of`. `received` requires obligation ID, revision ID, and observed revision SHA-256; `obligation_missing` forbids all three; `revision_missing` requires only obligation ID; `unreadable` requires obligation ID, revision ID, and `failure_code`. Snapshot ID/time are paired. Caller requests must omit seller-authored `recorded_at`.

Production webhook publishers may construct an unbound emitter and call `forTenantScope(trustedTenant)` before every delivery. Direct unbound `emit()` fails before checkpointing or network access. `createAdcpServer` derives scope from trusted request context; configure `webhooks.tenantScope` only for a genuinely single-tenant factory.
In-process HITL callbacks can write `return taskCtx.reject(result, reason)` for a business decline with an artifact but no structured execution error. `ctx.handoffToTask(producer, { settlement: 'external' })` is polling-only and must omit `push_notification_config`; its external producer context intentionally has no `reject()` and must durably queue the complete scoped handle before returning. Workers use `completeScopedTask()` or `failScopedTask()`, acknowledging only `applied` or compatible `already_terminal` outcomes and retrying or dead-lettering scope misses and conflicts. `createPostgresTaskSettlementCoordinator()` with `completeScopedPushTask()`, `failScopedPushTask()`, or `rejectScopedPushTask()` is a lower-level application-managed push-settlement API for integrations that already create and own both the task and protected push registration outside this framework handoff path.
When application state commits before SDK task settlement, call `createPostgresTaskSettlementIntentQueue().enqueue(intent, { db: tx })` in the same domain transaction. Acknowledgement discards the payload and retains an immutable fingerprint tombstone through the configured idempotency horizon; `pruneAcknowledged()` removes expired tombstones in bounded batches. Recovery callbacks are at-least-once. Use `applyTaskSettlementIntent()` to apply and prove the exact polling or push terminal artifact before returning `settled`. See `docs/guides/DURABLE-TASK-SETTLEMENT.md` for the supported workflow and scoped dead-letter operations.

## Task Statuses

Every tool call returns a `TaskResult` with one of these statuses:

- `completed` — Success. Data in `result.data`.
- `input-required` — Agent needs clarification. On A2A, when the seller returns a task ID, an `InputHandler` can continue the exchange and a handler-less call exposes `result.deferred.resume(answer)` for that exact task. A2A without a task ID and all MCP pauses return without invoking an input handler or attaching a resume closure; use an application/protocol-specific recovery path.
- `auth-required` — Agent requires refreshed authorization. Resume only when the returned A2A pause carries an exact-task continuation; otherwise use an application/protocol-specific recovery path.
- `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/`. Bearer auth uses `Authorization: Bearer <token>`; SDK clients also send the legacy `x-adcp-auth` header for compatibility, and servers accept it as a fallback.

**Deep dive:** [protocol differences](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) |
| Buyer quick start (AdCP 3.2) | docs/guides/BUYER-QUICKSTART-3.2.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/BUYER-QUICKSTART-3.2.md) |
| Verify proposal terms before acceptance | docs/guides/PROPOSAL-TERMS-VERIFICATION.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/PROPOSAL-TERMS-VERIFICATION.md) |
| Seller quick start (AdCP 3.2) | docs/guides/SELLER-QUICKSTART-3.2.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/SELLER-QUICKSTART-3.2.md) |
| Production durability checklist | docs/guides/PRODUCTION-DURABILITY.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/PRODUCTION-DURABILITY.md) |
| Persistent notification subscriptions | docs/guides/PERSISTENT-NOTIFICATION-RUNTIME.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/PERSISTENT-NOTIFICATION-RUNTIME.md) |
| Migrating SDK 13 → 14 | docs/migration-13-to-14.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-13-to-14.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) |
| AdCP 3.1.8 → 3.1.10 TMPX and Retina migration | docs/migration-adcp-3.1.8-to-3.1.10.md | [link](https://adcontextprotocol.github.io/adcp-client/migration-adcp-3.1.8-to-3.1.10.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) |
| Reporting source executor (seller adapters) | docs/guides/REPORTING-SOURCE-EXECUTOR.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/REPORTING-SOURCE-EXECUTOR.md) |
| Seller reporting ledger | docs/guides/REPORTING-LEDGER.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/REPORTING-LEDGER.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) |
| Durable task settlement intents | docs/guides/DURABLE-TASK-SETTLEMENT.md | [link](https://adcontextprotocol.github.io/adcp-client/guides/DURABLE-TASK-SETTLEMENT.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 | hosted only | [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
- SDK 14 CLI: `npx --package '@adcp/sdk@^14.0.0-0' adcp --help`; use the `adcp-3.1` tag only for the maintained 3.1 compatibility line