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.
AdCP schemas carry several x- prefixed annotation keywords that supplement the JSON Schema vocabulary. JSON Schema validators ignore unknown x- keywords per draft-07 §6, so adding these keys is wire-compatible with any conforming validator.
This page is the canonical reference for those annotations. Codegen consumers (TypeScript / Python / Go type generators), the storyboard runner, and the AdCP SDK family read these annotations programmatically; verifiers MAY read them but the normative behavior they describe is also documented in the relevant section of security.mdx or the field’s own description.
x-status
Marks a schema or property as experimental — part of the core protocol but not yet frozen. Sellers implementing experimental surfaces declare the feature id in experimental_features on get_adcp_capabilities. See Experimental Status for the full graduation policy.
"trusted_match": {
"type": "object",
"x-status": "experimental",
"description": "Trusted Match Protocol support..."
}
Allowed values: "experimental". The keyword is omitted on stable surfaces.
x-adcp-validation
Lifts structured normative constraints out of prose descriptions into a machine-readable shape. Storyboard runners and SDK validators consume the structured rules; codegen consumers can ignore the annotation and read the human description.
The keyword is most useful for fields whose description currently carries a “MUST be present when…” or “MUST equal the host of…” clause that the storyboard runner cannot enforce by parsing English.
Shape
"some_field": {
"type": "string",
"format": "uri",
"description": "Brief one or two sentences for codegen JSDoc; full constraints live in x-adcp-validation and the linked spec.",
"x-adcp-validation": {
"trust_root": true,
"required_when": { "any_of": [ ... ] },
"schema_required_when": { ... },
"verifier_constraints": { ... },
"distinct_from": "other.field.path",
"spec": "docs/.../section.mdx#anchor"
}
}
Sub-keys
| Key | Type | Purpose |
|---|
trust_root | boolean | Field is load-bearing for signature verification; verifiers MUST treat as authoritative. |
required_when | object wrapping any_of / all_of | Storyboard-enforced required-when rules (3.x). The object has exactly one of any_of (OR-joined) or all_of (AND-joined), each containing an array of leaf conditions. Each leaf is one of: { "field": "...", "non_empty": true }, { "field": "...", "equals": <value> }, { "field": "...", "any_subfield_present": true }. The wrapping object mirrors JSON Schema’s anyOf/allOf precedent so tooling readers can reuse familiar boolean-combinator semantics. Bare arrays are NOT accepted — always wrap. |
schema_required_when | condition | When the rule promotes from storyboard-enforced to schema-required. Typically keyed on adcp.supported_versions matching a version pattern via any_item_matches_pattern (e.g., "^4\\." for the 4.0 cutover and any 4.x patch). |
forbidden_when | object wrapping any_of / all_of | Inverse of required_when. The field MUST be absent (or false/empty, depending on type) when the wrapped condition holds. Same leaf shape as required_when. Use for fields whose presence is mutually exclusive with another posture. |
disjoint_with | string (dotted path) or array of dotted paths | Item-level mutual exclusion: no value in this field’s array MAY appear in any of the named arrays. Storyboard runners assert set-disjointness on each. Example: request_signing.warn_for carries disjoint_with: "request_signing.required_for" because an operation can be in one or the other, never both. |
subset_of | string (dotted path) | Item-level subset constraint: every value in this field’s array MUST also appear in the named array. Example: request_signing.required_for carries subset_of: "request_signing.supported_for" — an operation can’t be required without being supported. |
verifier_constraints | object | Free-form key-value map of verifier-side rules that don’t fit the structured sub-keys above. Keys are normative (e.g., agent_url_match: "byte_equal"); the storyboard runner enforces these against test vectors. Prefer the structured sub-keys (required_when, forbidden_when, disjoint_with, subset_of) when they fit; reach for verifier_constraints only for one-off rules that don’t generalize. |
distinct_from | string (dotted path) | Names another field that has a similar shape but different semantics, to defuse name confusion (e.g., identity.brand_json_url distinct from sponsored_intelligence.brand_url). Verifiers MUST NOT substitute one for the other. |
spec | string (relative path with anchor) | Pointer to the normative section in the docs that defines the field’s full semantics. Always required when other sub-keys are present. |
- Validators MUST ignore unknown sub-keys for forward-compatibility (the schema may add new entries in a minor release).
- The storyboard runner consumes
required_when, schema_required_when, and verifier_constraints to generate test cases per release; runners that don’t yet recognize a sub-key MUST skip it and emit an “unrecognized validation rule” warning.
- Codegen consumers (TypeScript / Python / Go type generators) MAY surface
x-adcp-validation.spec as a @see JSDoc link but otherwise treat the annotation as opaque.
Current usage
Representative usage:
| Field | Sub-keys used | Rule |
|---|
identity.brand_json_url | trust_root, required_when, schema_required_when, verifier_constraints, distinct_from | Trust-root pointer for signing-key discovery; required-when tied to signing posture; schema-required in 4.0; distinct from sponsored_intelligence.brand_url. See security.mdx §Discovering an agent’s signing keys. |
identity.key_origins | verifier_constraints (purpose_anchoring) | Every purpose listed MUST have a corresponding signing posture declared elsewhere on the response. Cross-field rule. See security.mdx §Origin separation. |
request_signing.required_for | subset_of | Every operation listed MUST also appear in supported_for — an operation can’t be required without being supported. |
request_signing.warn_for | disjoint_with, subset_of | An operation MUST NOT appear in both warn_for and required_for. Every operation listed MUST also appear in supported_for. |
webhook_signing.supported | verifier_constraints (must_equal_when) | When the seller advertises mutating-webhook emission (media_buy.reporting_delivery_methods includes webhook OR media_buy.content_standards.supports_webhook_delivery: true), supported MUST be true. Closes a downgrade vector. |
wholesale_feed_webhooks.event_types | verifier_constraints (wholesale_feed_webhook_capability_consistency) | product.* event types require wholesale get_products; signal.* event types require wholesale get_signals; wholesale_feed.bulk_change requires at least one declared wholesale repair path and must name only a repairable feed family. |
get_products.wholesale_feed_version / get_signals.wholesale_feed_version | verifier_constraints (required_for_wholesale_request) | Version tokens are required on wholesale read responses, but the shared response schemas cannot infer the request’s buying_mode / discovery_mode from the response body alone. |
Already enforced natively by JSON Schema and excluded from migration:
adcp.idempotency — the discriminated oneOf already requires replay_ttl_seconds in the supported branch and forbids it in the unsupported branch.
webhook_signing.algorithms — the enum: ["ed25519", "ecdsa-p256-sha256"] on each item already enforces the allowlist.
Migration history tracked at adcontextprotocol/adcp#3827.
x-adcp-open-payload
Classifies fields that look open-ended to SDK generators. The annotation is documentary and non-validating; the field’s JSON Schema still controls wire validation.
Allowed authoring values:
| Value | Meaning for schema authors and generators |
|---|
true | The field intentionally carries free-form payload data. Generators MAY model the object/decoded-JSON arm as Record<string, unknown> or another generic JSON container without warning. |
| Omitted | Unclassified. Generators and schema lint rules SHOULD NOT infer either true or false; they MAY warn on unannotated additionalProperties: true object fields during review. |
false is reserved for a future structured-but-extension-tolerant marker. Source schemas MUST NOT set x-adcp-open-payload: false until the repository defines at least one canonical use site and the generator contract for that value.
For mixed fields, the annotation applies only to the object or decoded-JSON payload arm. Scalar arms still follow their declared schema. For example, an upstream recorded body may be a decoded JSON object when the content type is JSON-shaped and a string otherwise; x-adcp-open-payload: true marks the decoded JSON payload as intentionally open, not the string arm as an object model.
"breakdown": {
"type": "object",
"x-adcp-open-payload": true,
"additionalProperties": true
}
Use omission for legacy or not-yet-classified fields; do not use omission to mean “closed”.
x-adcp-hoist
Build-time directive that marks a source schema as a canonically shared type. The schema bundler hoists every inline occurrence into a single root $defs entry and replaces inline copies with $ref pointers; the directive itself is stripped from bundled output. Wire-irrelevant — validators MUST ignore it (draft-07 §6 unknown-keyword semantics) and conforming consumers SHOULD NOT observe it on bundled artifacts.
{
"$id": "/schemas/core/price-block.json",
"title": "Price Block",
"x-adcp-hoist": true,
"type": "object",
"properties": {
"cpm": { "type": "number" },
"currency": { "type": "string" }
}
}
Why opt-in for complex objects
Pure enums hoist automatically (see hoistDuplicateInlineEnums) because merging two structurally-identical enums is semantics-preserving. Complex objects are different — structural identity ≠ semantic identity. BriefAsset (proposed creative spec) and VASTAsset (delivered video creative) currently share fields but represent different lifecycle concepts; auto-merging them would create cross-tool coupling the source schemas don’t express, and would be hard to unwind once SDKs codegen against the merged type. x-adcp-hoist makes the share-or-split decision deliberate per schema.
Bundler behavior
- Hoists at any occurrence count (≥1). The directive declares intent — “this is a canonical named type” — so adding a second reference later never changes the codegen surface.
title is required. Missing or empty title → build-time error. The directive is meant to be deliberate.
- Same title + different shape is a build-time error. Two marked schemas authored with the same
title but distinct fields would otherwise silently suffix one to Foo2, defeating the directive’s “canonical name” guarantee.
- Collision with a pre-existing
$defs key is suffixed (PriceBlock2), matching the convention used by the pure-enum hoist.
- Stripped from bundled output — both from the canonical
$defs entry and from any stray marker that was authored inside a pre-existing $defs block.
SDK / codegen impact
Adding x-adcp-hoist to a previously-inlined source schema is wire-compatible (the bundled schema still validates the same payloads) but is a codegen-shape change: TypeScript / Python / Go type generators that previously emitted an anonymous inline type (often Foo1, Foo2, …) will now emit a single named type. SDK adopters maintain rename aliases per their own deprecation policy — see adcp-client#942 for the client-side rename/alias tracking.
- Validators MUST ignore
x-adcp-hoist per draft-07 §6 (unknown keywords are tolerated). The directive has no wire semantics.
- Source-tree consumers (third parties that dereference
static/schemas/source/... directly rather than the bundled artifacts) MUST treat x-adcp-hoist: true as a no-op annotation. The schema’s content is the contract.
- Bundlers other than
scripts/build-schemas.cjs MAY honor the directive or ignore it; a bundler that ignores it produces a wire-compatible bundle with un-deduped inline copies.
History
Future extensions
New x-adcp-* keywords are added in minor releases. Consumers MUST tolerate unknown x- keywords without erroring. The convention reserves the x-adcp- namespace; vendor-specific or deployment-specific annotations SHOULD use a vendor-specific prefix (e.g., x-yourorg-) to avoid collision.