Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt

Use this file to discover all available pages before exploring further.

When a compliance storyboard fails against your agent, the runner reports a step name and error text. This page maps the most common error patterns to their root causes and fixes, so you can resolve each class of failure without spelunking through SDK source or runner internals. Each section shows the error you’ll see, what it means, and what to change in your agent.

Unknown fixture errors

× (unknown step): PRODUCT_NOT_FOUND: Package 0: Product not found: test-product
The storyboard’s sample_request references a hardcoded ID (test-product, test-pricing, campaign_hero_video, gov_acme_q2_2027, etc.). The runner expects the agent to have that ID in its catalog before the mutating step runs. Fix: Implement comply_test_controller and honor the seed scenarios declared in the storyboard’s fixtures: block. When prerequisites.controller_seeding: true is set, the runner auto-injects a fixtures phase that calls seed_product, seed_pricing_option, seed_creative, seed_plan, or seed_media_buy in foreign-key order before the main phases execute. See Compliance test controller — Scenarios for the full seed contract. Agents that return UNKNOWN_SCENARIO on a seed call grade the storyboard not_applicable — they are not penalized for missing sandbox surface, but they cannot pass storyboards that depend on pre-seeded state.

Signature challenge missing on 401

× (unknown step): expected error="request_signature_required", got error="(none)"
The storyboard sent an unsigned request to an operation declared in get_adcp_capabilities.request_signing.required_for. Your agent rejected with 401 but did not include a WWW-Authenticate: Signature ... challenge header, so the runner could not resolve the error code from the transport binding. Fix: Emit the RFC 9421 challenge header on every 401 caused by missing or invalid signatures. The runner resolves the error code via the transport binding order — if the WWW-Authenticate header is absent, the error classification falls back to “(none)” even when the JSON body carries a useful message. The reference SDK constructs these errors via RequestSignatureError from @adcp/sdk/signing with .code: RequestSignatureErrorCode. The full taxonomy (request_signature_required, request_signature_header_malformed, request_signature_tag_invalid, request_signature_window_invalid, request_signature_key_unknown, etc.) is enumerated in that module. Your agent SHOULD surface the same code on the challenge so SDK-speaking callers can recover automatically. See Signed Requests (Transport Layer) for the challenge-header format and the transport-error binding order.

Response envelope drift

× (unknown step): Response contains errors array
The vector used check: error_code but your response surfaces the error on a shape the runner’s client-detection order didn’t expect. In practice, this means your agent returned errors[] when the transport layer already carried adcp_error, or vice versa — the storyboard asserted a single error code and the runner resolved it from a different layer than you emitted on. Fix: Pick one error surface per response and stick to it per the envelope vs. payload two-layer model. On MCP: adcp_error for structured content; errors[] for task-payload errors. On A2A: the same layers apply — transport error in the envelope, application error in the task artifact’s DataPart. The runner’s check: error_code is shape-agnostic — it resolves from either layer — but if your agent emits both simultaneously they can disagree, and the runner grades the resolved code against the vector’s expectation. Choosing one surface and being consistent avoids the divergence.

Context echo failures

× (unknown step): expected field "context.correlation_id" = "xyz", got (missing)
Your agent returned a response that does not include the context: object from the request. Every storyboard step that sends context: { correlation_id: ... } asserts that context.correlation_id echoes unchanged in the response. Fix: Preserve the full context: object verbatim on every response, including errors. The echo contract is normative — buyers use correlation_id to stitch multi-agent flows, and the runner grades every context-carrying step on it. See Context and sessions — Normative echo contract. Captures use the same contract in reverse: storyboards that pass "$context.<name>" through context_outputs: rely on the capture populating after the producer step’s validations pass. A downstream step reading $context.foo when the producer failed or omitted context: grades as unresolved_substitution.

Capability-vector mismatch (runner declared, agent doesn’t support)

× (unknown step): capability X asserted but not declared in get_adcp_capabilities
The storyboard dispatched a step that requires a capability your agent does not advertise in its get_adcp_capabilities response. The runner should auto-skip these steps; if you’re seeing them graded as failures instead, either the capability is declared at the wrong key or the runner is missing the auto-skip path. Fix: Double-check your get_adcp_capabilities.tools list and any required-for fields (request_signing.required_for, idempotency.supported_tools, etc.). For vectors that apply only to specialized agents, the storyboard author can use skipVectors to flag the opt-out explicitly; as an implementer, the fix is almost always on the capability declaration rather than the vector.

Required-for composition

× (unknown step): missing auth — step requires authenticated or signed
The runner encountered a mutating step that expected either authenticated credentials or a signed request, and the transport carried neither. Typically this means the test kit didn’t declare auth.api_key or auth.basic AND the agent doesn’t advertise request-signing support — leaving the runner with no way to authenticate the call. Fix: Either (a) declare auth.api_key or auth.basic in the test kit so the runner uses a static Authorization-header credential, or (b) advertise request-signing via get_adcp_capabilities.request_signing so the runner signs the request instead. The runner’s requireAuthenticatedOrSigned gate accepts either path — it fails only when both are absent.

Static-credential agent: no auth mechanism contributed (assert_mechanism)

{
  "check": "assertion",
  "passed": false,
  "description": "Probe validations failed.",
  "expected": ["auth_mechanism_verified"],
  "actual": []
}
The mechanism_required phase found no contribution to auth_mechanism_verified from any optional auth phase. This is the most common failure for static-credential-only agents (Bearer API key or HTTP Basic), and it is not a real auth problem — it is a test-kit configuration gap. What happened: The api_key_path phase has skip_if: "!test_kit.auth.api_key" and the basic_path phase has skip_if: "!test_kit.auth.basic" — each is skipped unless the test kit declares the matching credential. The oauth_discovery phase 404s on /.well-known/oauth-protected-resource/... (expected for static-credential-only agents; those failures are silently ignored by the runner). With all optional auth phases contributing nothing, assert_mechanism sees actual: []. The --auth TOKEN distinction: The --auth TOKEN flag you pass to the runner is the runner’s own session credential — it authorizes the runner’s own requests to your agent. It is entirely separate from test_kit.auth.api_key or test_kit.auth.basic, which are the specific credentials the static-credential phases send during positive and invalid-credential probes. These are not the same token and are not interchangeable. Fix for Bearer API key agents: Every default AdCP brand test kit declares its probe API key under auth.api_key using the demo-<kit>-v1 naming convention. The default test kit (acme-outdoor) uses demo-acme-outdoor-v1. Configure your agent to accept the kit’s probe key as a valid compliance-testing credential alongside your production key. The demo-<kit>- prefix is the AdCP conformance handle — accept any Bearer token matching the prefix for the kits you run against (the suffix can rotate across spec versions, the prefix stays stable):
serve({
  authenticate: verifyApiKey({
    keys: {
      [PRODUCTION_TOKEN]: { principal: 'my-principal' },
      // Accept the default compliance kit's probe key (and any future suffix rotation)
      'demo-acme-outdoor-v1': { principal: 'compliance-runner' },
    }
  })
})
Fix for HTTP Basic agents: Use a test kit that declares auth.basic with either username/password or a single credentials value containing the unencoded username:password pair. Configure your agent to accept that Basic credential as a valid compliance-testing credential alongside your production Basic credential. The basic_path phase then sends the valid Basic credential and a random-invalid Basic credential, and contributes auth_mechanism_verified only when the agent accepts the valid credential and rejects the invalid one. Agents that only accept their own production credential skip the matching static path (no test-kit credential matches), fail oauth_discovery (no PRM), and land at actual: [] in assert_mechanism. Adding the test-kit credential to your allowed-credentials set is sufficient — you do not need a PRM endpoint or an OAuth issuer. Do not serve a fake /.well-known/oauth-protected-resource/... pointing at a non-existent issuer to “pass” the OAuth phase. That triggers the advertised-but-unserved failure mode the storyboard was designed to catch. See Known spec ambiguities — PRM required for non-OAuth agents for background on why the carve-out exists and how the static-credential / oauth_discovery phase semantics were designed.

INVALID_STATE vs INVALID_TRANSITION

Two codes that are easy to confuse:
  • INVALID_STATE — the canonical AdCP media-buy error code for “the resource is in a state that doesn’t allow this action.” Used on create_media_buy/update_media_buy/pause/resume/cancel against a media buy that cannot transition as requested. See media-buy/specification.mdx and media-buy/media-buys/index.mdx for authoritative usage.
  • INVALID_TRANSITION — specific to the comply_test_controller sandbox primitive. Emitted when a runner requests a state-machine transition the seller rejects (e.g., forcing approvedarchived without going through active). See Compliance test controller — Scenarios.
A storyboard vector that asserts INVALID_STATE on a production task but your agent returns INVALID_TRANSITION is an error-code vocabulary mismatch — INVALID_TRANSITION is not in the canonical enum at static/schemas/source/enums/error-code.json and should not appear outside the compliance test controller.

When none of the above matches

If you hit a failure that doesn’t map to anything here, check the known spec ambiguities page — some storyboards are blocked on resolved-but-unreleased spec gaps, and the workaround is tracked there. Still stuck? File an issue at adcontextprotocol/adcp with the full runner output and the storyboard name. Maintainers can usually narrow the pattern from the error signature.