Error code for programmatic handling. The error-code vocabulary is open: error.code is wire-typed string (not a closed enum), the standard codes published in enums/error-code.json are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read error.recovery for the recovery classification, and fall back to transient when recovery is absent. See error-handling.mdx#forward-compatible-decoding-normative for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.
Human-readable error message
OptionalfieldField path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When issues[] is also present, sellers MUST set this to issues[0].pointer translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading field only get deterministic behavior. Will be deprecated in a future major version in favor of issues[].pointer.
OptionalsuggestionSuggested fix for the error
Optionalretry_Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.
OptionalissuesStructured list of validation failures. Primary use is VALIDATION_ERROR, where multi-field rejections are common and field (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When issues is present, sellers MUST also populate field from issues[0] for backward compatibility with pre-3.1 consumers that read field only — translating the RFC 6901 pointer format to the JSONPath-lite format field uses (e.g., /packages/0/targeting → packages[0].targeting). MUST (not SHOULD) so consumers reading field get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate field in favor of issues[].pointer.
RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (instancePath); standardized and unambiguous on keys containing / or ~. NOTE: this differs from the legacy top-level field which uses JSONPath-lite (packages[0].targeting.geo_countries[2]). When sellers populate field from issues[0].pointer for backward compatibility (see field description), they MUST translate the format — /packages/0/x → packages[0].x. Future major versions will deprecate field in favor of issues[].pointer.
Human-readable description of why this specific field was rejected.
Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here.
OptionalschemaPath?: stringOptional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer schema_id; schemaPath is retained for 3.0.x compatibility (renamed to schema_path in a future major). See error-handling.mdx for the validator-internals production-emit rules.
Optionalschema_id?: stringOptional. $id of the rejecting (sub-)schema (e.g. /schemas/3.1.0/core/activation-key.json). MUST resolve to a $id published in the spec at the version the seller advertises via get_adcp_capabilities — either a deep sub-schema (the typical case) or the response-root $id (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating schemaPath SHOULD also populate schema_id when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat.
Optionaldiscriminator?: { property_name: string; value: string | number | boolean | null }[]Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated oneOf / anyOf and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. (type, value_type)) produce multiple entries ordered by declaration in the rejecting schema's properties block. Same private-extensions / version-skew carve-out as schema_id. See error-handling.mdx.
OptionaldetailsAdditional task-specific error details. Sellers MAY mirror issues[] here as details.issues for backward compatibility with pre-3.1 consumers reading from details; new consumers SHOULD prefer the top-level issues field.
Canonical rejection-set shape (3.1+). When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key accepted_values: <array> under details rather than seller-specific variants observed in the wild (available, allowed, accepted_values at the error root, etc.). The canonical shape:
{
"code": "INVALID_PRICING_MODEL",
"message": "Pricing option not found: po_prism_abandoner_cpm",
"field": "pricing_option_id",
"details": {
"rejected_value": "po_prism_abandoner_cpm",
"accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]
}
}
rejected_value (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).accepted_values (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what's accepted for this caller in this context (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.This is SHOULD-level guidance, not MUST: details remains additionalProperties: true and pre-3.1 sellers using available / allowed / accepted_values at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.
OptionalrecoveryAgent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate recovery on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize error.code (a newer code, or a platform-specific code) MUST still be able to classify the error from recovery. The enumMetadata.recovery block in enums/error-code.json is the documentary mirror for known codes; error.recovery on the wire is authoritative.
OptionalsourceWho emitted this error entry. producer (default when absent): emitted by the response's authoring agent (the seller for get_products, the creative agent for build_creative, etc.). sdk: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., FORMAT_PROJECTION_FAILED when the buyer SDK couldn't project a v1 format to a canonical, or FORMAT_DECLARATION_DIVERGENT when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set sdk_id so downstream consumers can identify which intermediate processor inserted the entry.
Multi-hop propagation (normative). AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments errors[] with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by (code, field) rather than re-emit; the existing entry's sdk_id identifies which earlier processor saw it first. Producer entries (those without source: "sdk") are authoritative for what the response's authoring agent self-detected; SDK entries are observations made on top.
Replay/audit safety. Persisted or replayed responses carry source and sdk_id so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without source, a downstream consumer can't tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.
Optionalsdk_Optional identifier for the SDK that augmented this error entry. Format: <sdk_package_name>@<version> (e.g., @adcontextprotocol/adcp@7.3.0, adcontextprotocol-adcp-python@1.2.0). MUST be set when source: "sdk"; MUST be absent when source: "producer" or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK's registry version doesn't).
Transport-envelope error signal for fatal task failures. Per the two-layer model in
error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model, a fatal task failure SHOULD populate both this envelope-level field AND the payload'serrors[]array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structurederrors[]remains the canonical normative shape. Non-fatal warnings populate ONLYpayload.errors[]withseverity: warning— the envelope MUST NOT carryadcp_errorfor non-failures.