diff --git a/.changeset/brand-verification-rfc.md b/.changeset/brand-verification-rfc.md
new file mode 100644
index 0000000000..d985133fb0
--- /dev/null
+++ b/.changeset/brand-verification-rfc.md
@@ -0,0 +1,18 @@
+---
+---
+
+Draft RFC for the brand verification surface — three interrogative brand-agent tasks that let partners ask the brand authoritatively whether something belongs to it.
+
+The RFC lives at `docs/brand-protocol/proposals/brand-verification-rfc.mdx`. Tracks the federated trust capability discussed in [#4521](https://github.com/adcontextprotocol/adcp/issues/4521) and supersedes the email-based self-healing path landed in PR #4505. Not yet normative — needs spec-owner sign-off before any agent implementations standardize.
+
+New tasks proposed (all on the brand protocol surface, advertised in `get_adcp_capabilities` `supported_tasks`):
+
+- `verify_subsidiary_claim` — "Is this brand a subsidiary of yours?" Replaces crawl-based mutual-assertion inference with the brand-agent's authoritative answer, including the `pending_review` and `disputed` states crawl cannot express.
+- `verify_property` — "Is this site / app / property actually one of yours?" Returns ownership + the property-relationship enum (`owned` / `direct` / `delegated` / `ad_network`) plus optional per-use-case authorization.
+- `verify_trademark` — "Is this trademark one of yours?" Returns ownership, licensing relationship, jurisdictions, Nice classes, and optional use-case authorization.
+
+Shared `VerificationStatus` enum captures the rich state surface (`owned`, `pending_review`, `disputed`, `not_ours`, `licensed_in`, `licensed_out`, `unknown`). Public/authorized tier split mirrors `get_brand_identity`.
+
+Cross-protocol Conformance addition to `brand.json`: when a house publishes a brand-agent advertising these tasks, consumers SHOULD prefer the agent's signed response over crawl-based mutual-assertion inference. The crawl path remains the fallback when the agent is unreachable or returns `unknown`.
+
+Schema additions: `core/verification-status.json`, three request schemas, three response schemas. No changes to `brand.json` itself. Additive — every existing publisher and every existing brand-agent continues to work unchanged.
diff --git a/docs.json b/docs.json
index becb03393f..4614e1aa24 100644
--- a/docs.json
+++ b/docs.json
@@ -498,10 +498,19 @@
"group": "Tasks",
"pages": [
"docs/brand-protocol/tasks/get_brand_identity",
+ "docs/brand-protocol/tasks/verify_subsidiary_claim",
+ "docs/brand-protocol/tasks/verify_property",
+ "docs/brand-protocol/tasks/verify_trademark",
"docs/brand-protocol/tasks/get_rights",
"docs/brand-protocol/tasks/acquire_rights",
"docs/brand-protocol/tasks/update_rights"
]
+ },
+ {
+ "group": "Proposals",
+ "pages": [
+ "docs/brand-protocol/proposals/brand-verification-rfc"
+ ]
}
]
},
@@ -1064,6 +1073,9 @@
"group": "Tasks",
"pages": [
"docs/brand-protocol/tasks/get_brand_identity",
+ "docs/brand-protocol/tasks/verify_subsidiary_claim",
+ "docs/brand-protocol/tasks/verify_property",
+ "docs/brand-protocol/tasks/verify_trademark",
"docs/brand-protocol/tasks/get_rights",
"docs/brand-protocol/tasks/acquire_rights",
"docs/brand-protocol/tasks/update_rights"
diff --git a/docs/brand-protocol/brand-json.mdx b/docs/brand-protocol/brand-json.mdx
index 51371097e3..0b2854701c 100644
--- a/docs/brand-protocol/brand-json.mdx
+++ b/docs/brand-protocol/brand-json.mdx
@@ -1183,6 +1183,7 @@ These invariants MUST be enforced by validators and crawlers; JSON Schema cannot
**Self-healing**
- **Leaf-only edges SHOULD trigger notification.** When a consumer encounters a leaf claiming `house_domain: X` with no reciprocal entry on X, the consumer SHOULD notify X's `contact.email` so the house team can complete the reciprocation. Notifying parties MUST rate-limit per `{leaf, house}` pair to avoid flooding.
+- **Agent-preferred verification (proposed — see [brand verification RFC](/docs/brand-protocol/proposals/brand-verification-rfc)).** When the named house publishes a brand-agent advertising `verify_subsidiary_claim` (or `verify_property` / `verify_trademark`) in its `supported_tasks`, consumers SHOULD prefer the agent's authoritative response over crawl-based mutual-assertion inference. The agent's response is signed under the brand's `adcp_use: "response-signing"` JWK; signed `disputed` or `not_ours` from the agent overrides any leaf-side `house_domain` claim. The crawl path remains as fallback when the agent is unreachable or returns `unknown`. Not yet normative; the email notification SHOULD continues to apply for houses without a brand-agent.
## Prior art
diff --git a/docs/brand-protocol/proposals/brand-verification-rfc.mdx b/docs/brand-protocol/proposals/brand-verification-rfc.mdx
new file mode 100644
index 0000000000..8b58ba835d
--- /dev/null
+++ b/docs/brand-protocol/proposals/brand-verification-rfc.mdx
@@ -0,0 +1,344 @@
+---
+title: "RFC — Brand verification surface"
+description: "Proposal for a federated authoritative verification surface on the brand-agent. Three interrogative tools — verify_subsidiary_claim, verify_property, verify_trademark — let partners ask the brand authoritatively whether something belongs to it."
+"og:title": "AdCP — Brand verification RFC"
+---
+
+
+**RFC — discussion in progress.** Not yet normative. Tracks the federated trust capability discussed in [#4521](https://github.com/adcontextprotocol/adcp/issues/4521) and the spec-side follow-ups from PR [#4505](https://github.com/adcontextprotocol/adcp/pull/4505).
+
+
+## Summary
+
+Add three interrogative tools to the brand-protocol surface that let partners ask a brand's authoritative agent whether something belongs to it:
+
+- **`verify_subsidiary_claim`** — "Is this brand a subsidiary of yours?"
+- **`verify_property`** — "Is this site / app / property actually one of yours?"
+- **`verify_trademark`** — "Is this trademark one of yours?"
+
+These supersede the email-based [self-healing notification](/docs/brand-protocol/brand-json#self-healing-through-notification) that PR #4505 landed (the email SHOULD remains as a fallback when no brand-agent is advertised). A consumer (DSP, crawler, partner agent) detecting a leaf-only edge no longer pushes a notification at the house and waits — it asks the brand's agent directly and gets an authoritative answer.
+
+The mutual-assertion crawl model stays the decentralized backstop. The brand-agent verification path is the federated authoritative alternative for brands that opt in.
+
+The metaphor is **DRM-shaped for brand identity**: the brand owns its own answers to "what's mine," and partners ask before they act. It's not gating access to public data — the data is still public — it's providing nuanced, authoritative answers that crawling cannot. The substance is **federated authoritative verification**; DRM is the analogy, not the framing for spec readers.
+
+## Motivation
+
+`brand.json`'s mutual-assertion model is decentralized — two parties publish their halves at well-known URLs and a consumer crawls both to confirm. It's the right primitive for the open web, but it has known gaps:
+
+- **Two-state visibility.** Crawling answers "mutual or not." It can't surface *pending review*, *transferring* (M&A in flight), *disputed*, or *licensed in*. Brands need a richer state surface.
+- **TTL-bound freshness.** A consumer's view is only as fresh as its last crawl. M&A-driven changes don't propagate until the next refresh.
+- **One-sided pessimism.** When a leaf claims a parent and the parent hasn't reciprocated, the leaf gets downgraded to "claimed, unverified." Even if the parent's intent is to confirm, the protocol can't surface that intent before the file edit propagates.
+- **No partner-side double-check primitive.** A "good partner" — a DSP about to extend brand-safety trust through a leaf, a creative-clearance pipeline confirming a trademark — has no way to ask the brand "is this really yours, with my use case in mind?" before acting.
+
+**What this surface does fix.** Partners get typed, authoritative answers including the rich state crawling can't express. Stale crawler caches are no longer the trust floor.
+
+**What it doesn't fix.** The sub-brand self-publishing problem (Converse team waiting for Nike's portfolio team to add the reciprocal entry, from PR #4505) is **relocated** by this surface, not solved. The partner gets `pending_review` instead of silence — a real UX improvement — but Converse still needs Nike's portfolio team to act. The agent-side workflow is the brand's responsibility; the protocol surfaces the state, it can't accelerate the parent's decision.
+
+## Proposal
+
+### Three tools, one shape
+
+Each tool follows the same shape:
+
+- **Input:** the claim being verified (subsidiary domain, property, trademark)
+- **Output:** an enum status from the shared `VerificationStatus` vocabulary, plus tool-specific context
+
+The status enum captures the rich state the crawl model can't express:
+
+| Status | Meaning |
+|---|---|
+| `owned` | Definitively belongs to this brand, currently. |
+| `pending_review` | Claim known to the brand; no decision yet. **MUST be returned with `expected_resolution_window_days`**; the agent MUST transition to a terminal status or `unknown` once the window elapses. Enforcement is agent-side (not spec-level) — see [pending_review aging](#pending_review-aging-and-the-crawl-safety-net) below. |
+| `transferring` | Ownership is provably changing — M&A in flight, divestiture closing, known imminent transition. Distinct from `pending_review` (under-review-by-us); `transferring` signals "the answer is known to be becoming something else." |
+| `disputed` | The brand actively rejects this claim. |
+| `not_ours` | The brand affirms it is not their property / subsidiary / mark. |
+| `licensed_in` | This brand uses the asset under license (response carries `licensor_domain`). |
+| `licensed_out` | This brand licenses the asset to another entity. |
+| `unknown` | The agent has no position. Caller MAY fall back to crawl. |
+
+Each tool's response schema **constrains** the enum to the statuses applicable to that tool — not every status applies to every tool. Subsidiaries aren't licensed; properties don't have `pending_review` (use `transferring` for in-flight ownership); trademarks are mostly definitive but can be `transferring` during M&A.
+
+### `verify_subsidiary_claim`
+
+**Like `verify_property`, this is a gate before you proceed.** Inventory onboarding, brand-relationship establishment, creative clearance, member-feature provisioning — anywhere a downstream system would extend governance trust through a brand → house relationship, this call gates that step. You check the claim with the named house and act on the answer, you don't post-hoc reconcile a state after the fact.
+
+A consumer detects a leaf-only edge: `converse.com` claims `house_domain: "nikeinc.com"`, but Nike's `brand_refs[]` doesn't include it yet. Today the consumer is told to email `contact.email` and wait. With this tool:
+
+```json
+// Request to nikeinc.com's brand-agent
+{
+ "claimed_subsidiary_domain": "converse.com",
+ "claimed_brand_id": "converse"
+}
+```
+
+```json
+// Response (mutual, signed by Nike's agent)
+{
+ "status": "owned",
+ "brand_id": "converse"
+}
+```
+
+```json
+// Response (pending — MUST include expected_resolution_window_days)
+{
+ "status": "pending_review",
+ "brand_id": "converse",
+ "first_observed_by_house_at": "2026-05-12T14:00:00Z",
+ "expected_resolution_window_days": 7
+}
+```
+
+The agent's signed response is authoritative. The crawl backstop becomes unnecessary for the partner who has this answer.
+
+### `verify_property`
+
+**This is a prerequisite check, not a mid-flight signal.** Verification gates the *next step* in a workflow: you check before you proceed, not after. Sub-100ms auction budgets don't permit MCP round-trips, so this is never bid-time. It runs at decision points where you can still say no:
+
+- **Inventory onboarding** — an SSP adds a property to its catalog; the buyer requires brand confirmation before allowing bidding on it. The verification gates the catalog entry.
+- **Creative clearance** — a creative-approval workflow encounters a property reference; verification gates approval.
+- **Fraud detection** — a domain is suspected of falsely claiming brand affiliation; verification gates whatever escalation path follows (block, flag, investigate).
+
+The gating semantics are what justifies the round-trip cost. If you're consuming the answer after a decision is locked in, you're using the wrong tool — `brand.json` properties[] inference is the read-only signal for that case.
+
+Cache the answer (24-72h is appropriate for `owned`/`not_ours`); re-check at the next gate, not every bid.
+
+```json
+// Request
+{
+ "property": {
+ "type": "website",
+ "identifier": "nike.cn"
+ }
+}
+```
+
+```json
+// Response (owned)
+{
+ "status": "owned",
+ "relationship": "owned",
+ "brand_id": "nike",
+ "regions": ["CN"],
+ "context_note": "Regional site for China market"
+}
+```
+
+The `relationship` field mirrors `brand.json`'s [property relationship enum](/docs/brand-protocol/brand-json#property-relationships) (`owned`, `direct`, `delegated`, `ad_network`).
+
+### `verify_trademark`
+
+DoubleVerify, IAS, and creative-clearance pipelines already check trademark posture from registry crawls. **The differentiator here is the brand's authoritative statement about its licensee posture and authorized use cases** — registries can't tell you which use cases a brand authorizes for a mark, who the licensor is on a licensed-in mark, or whether the mark is mid-transfer. That's the gap this tool closes.
+
+```json
+// Request
+{
+ "mark": "AIR JORDAN",
+ "registry": "USPTO",
+ "number": "1234567"
+}
+```
+
+```json
+// Response (owned, authorized caller with use cases)
+{
+ "status": "owned",
+ "matched_registration": {
+ "registry": "USPTO",
+ "number": "1234567",
+ "mark": "AIR JORDAN",
+ "registration_status": "active"
+ },
+ "countries": ["US", "EU", "JP"],
+ "nice_classes": [25, 41],
+ "use_case_authorization": {
+ "advertising": true,
+ "endorsement": true,
+ "merchandise_resale": false
+ }
+}
+```
+
+For licensed marks, `status: "licensed_in"` carries `licensor_domain`. The full schema covers the same shape as `brand.json`'s [`#/definitions/trademark`](/docs/brand-protocol/brand-json#trademarks), which this tool's responses reuse where applicable.
+
+## Discovery and routing
+
+Existing AdCP convention: a brand-agent advertises its tasks via `get_adcp_capabilities`. The verification tools surface as additional task names alongside `get_brand_identity`:
+
+```json
+{
+ "supported_protocols": ["brand"],
+ "supported_tasks": [
+ "get_brand_identity",
+ "verify_subsidiary_claim",
+ "verify_property",
+ "verify_trademark"
+ ]
+}
+```
+
+Brand-agent implementations are free to support all, some, or none. A brand without a brand-agent (variants 1, 2, 4, 5 with no `agents[]` entry, or an agent that doesn't advertise these tasks) falls back to the existing crawl-based mutual-assertion model and the `contact.email` SHOULD from PR #4505.
+
+When a brand-agent is advertised and the relevant tool is in `supported_tasks`, the [Conformance](/docs/brand-protocol/brand-json#conformance) clause around mutual-assertion verification SHOULD prefer the agent's authoritative answer over the crawl-based inference. The crawl path remains the default; the agent path is a discoverable upgrade.
+
+## Authorization tiers
+
+Verification tools follow the same public/authorized split as `get_brand_identity`. **The tier table here is the authoritative reference; task pages refer back to it.**
+
+| Tier | What the agent returns |
+|---|---|
+| **Public** (no linked account) | `status` (always). For owned/transferring cases: `brand_id`, `relationship` (property), `matched_registration` + `countries` + `nice_classes` (trademark). For `disputed`/`not_ours`/`pending_review`: `context_note`. |
+| **Authorized** (linked via [`sync_accounts`](/docs/accounts/tasks/sync_accounts)) | Everything above, plus: `first_observed_by_house_at`, `expected_resolution_window_days`, `use_case_authorization`. |
+
+Authorized tier is **a wedge for `sync_accounts` adoption**. The `use_case_authorization` map — does the brand authorize this use case for this asset? — is the highest-value field for a DSP that's already shopping inventory with the brand. It's not derivable from any registry crawl. Account-linking gets the partner the answer registries can't give.
+
+Queue position, internal ticket state, and team routing are NEVER exposed — not to the public, not to authorized callers.
+
+## Trust model
+
+The agent's response is **authoritative**: signed under the brand's `adcp_use: "response-signing"` JWK (matching the AdCP webhook-signing convention — distinct from `request-signing` per the [keys-per-purpose](https://datatracker.ietf.org/doc/html/rfc9421) requirement). Consumers verify the signature against the brand's published JWKS before extending trust.
+
+**Normative conflict resolution.** When agent and crawl disagree:
+
+| Agent says | Crawl observes | Consumer treats as |
+|---|---|---|
+| `owned` | Mutual or leaf-only | Trusted edge. Governance propagation: yes. |
+| `pending_review` | Leaf-only (parent silent) | Trusted leaf identity; relationship trust withheld. When the response's `expected_resolution_window_days` is exceeded without transition, consumer SHOULD treat as if `unknown` returned and fall back to crawl-based inference. |
+| `transferring` | Any | Trusted but in-flight; consumers MAY treat as `owned` for stability and SHOULD surface the transition in UI. |
+| `disputed` | Leaf-only or mutual | The brand has spoken. **Leaf is treated as having a rejected parent claim.** UI: "Brand X says this is not theirs." |
+| `not_ours` | Leaf-only or mutual | The brand affirms no relationship. **Leaf treated as standalone with a disputed parent claim, regardless of leaf's `house_domain` declaration.** UI surfaces the rejection. |
+| `unknown` | Any | Fall back to crawl-based mutual-assertion inference. |
+
+**When the agent answers, the agent wins.** This is normative, not advisory. The crawl path remains the fallback only when the agent is unreachable, returns `unknown`, or doesn't implement the task.
+
+A leaf publisher whose `house_domain` claim is rejected by the named house's agent (`disputed` or `not_ours`) has **no protocol-level recourse** to override the rejection. The leaf MAY update its `house_domain` (or remove it for standalone status) and re-publish; the brand-side response will update on the agent's next refresh. UI guidance for rendering this state on consumer surfaces is a [follow-up](https://github.com/adcontextprotocol/adcp/issues) (tracked separately).
+
+## Caching (normative)
+
+Verification responses SHOULD be cacheable per standard HTTP semantics. Agents SHOULD set `Cache-Control: max-age=N` appropriate to the volatility of the answer:
+
+- `owned` / `not_ours` / `disputed` — stable. 24-72h is reasonable.
+- `pending_review` — volatile. Re-check on each governance decision; max-age SHOULD be short (≤1h).
+- `transferring` — volatile. Re-check until status transitions; max-age SHOULD be short (≤4h).
+- `licensed_in` / `licensed_out` — moderately volatile. 24h is reasonable; re-check on contract-affected workflows.
+- `use_case_authorization` — most volatile field. SHOULD be re-checked per session.
+- `unknown` — short cache only (≤1h) so consumers can re-query as the agent's knowledge grows.
+
+Consumers MAY override agent-supplied cache hints downward. A consumer SHOULD NOT cache beyond the agent's supplied `max-age`.
+
+## `pending_review` aging and the crawl safety net
+
+The aging contract — `pending_review` MUST come with `expected_resolution_window_days` AND the agent MUST transition to a terminal status (`owned` / `disputed` / `not_ours`) or to `unknown` once the window elapses — reads cleanly as a normative MUST. The enforcement story is more honest:
+
+- **Enforcement is agent-side, not spec-level.** No external party validates the aging contract. The protocol can't compel a brand's portfolio team to action a queue, and the spec doesn't try.
+- **Day-one deployments will lag.** Most brand-side implementations (especially self-hosted holdcos) won't ship with automated `pending_review → unknown` flipping on launch. Portfolio teams operate at human-week timescales; flipping stale claims is not the first automation they build.
+- **The safety net is the crawl fallback, not the spec.** When an agent returns stale `pending_review` past its declared window, the **consumer** treats the response as effectively `unknown` and falls back to crawl-based mutual-assertion inference. This is the same fallback consumers use when the agent is unreachable or genuinely returns `unknown` — it requires nothing new on the consumer side.
+- **MUST is the right normative language anyway.** It states declared intent and a defined fallback. That's better than the status quo, where `pending` limbo has no expiry and no recourse. A noisy `pending_review` from an under-automated agent is recoverable; silence isn't.
+
+In summary: agents SHOULD transition or flip past the window; consumers MUST tolerate agents that don't, by falling back to crawl. The aging contract is real, the enforcement is realistic, the fallback is graceful.
+
+## Rate limiting (normative expectation)
+
+Agents MAY rate-limit per `{caller_identity, tool, query_target}`. Agents SHOULD:
+
+- Return `Retry-After` header on rate-limited responses so callers can back off cleanly.
+- Prefer returning the cached prior answer (with a fresh `Cache-Control` allowing the caller to use it) over a `RATE_LIMITED` error — graceful degradation beats hard failure.
+
+Callers' `idempotency_key` (from `version-envelope`) lets retries deduplicate; agents SHOULD recognize repeated keys within their TTL window.
+
+## Deployment model
+
+Two practical deployment shapes:
+
+**Hosted by AAO (most members).** AdContextProtocol.org operates brand-agents as a managed service for members. AAO ingests member-supplied data + portfolio-team workflows and exposes the verification tools per the standardized contract. Members configure their answers; the protocol surface is uniform.
+
+**Self-hosted.** Holdcos, agencies, and any party with engineering bandwidth implements the brand-agent themselves. They publish the agent URL in their brand.json `agents[]`, sign responses with their own `response-signing` JWK, and implement whatever queue/triage system fits their portfolio operations.
+
+**No brand-agent.** Most brands today. Falls back to the crawl-based mutual-assertion model plus the email-based self-healing SHOULD from PR #4505. The protocol degrades cleanly.
+
+This RFC defines the contract; the AAO product team's implementation is tracked separately as a [follow-up](https://github.com/adcontextprotocol/adcp/issues).
+
+## End-to-end example: Nike, Inc.
+
+A complete walkthrough for a partner verifying the full Nike portfolio. Assumes Nike publishes a brand-agent at `https://agent.nikeinc.com/mcp` advertising all three verification tools.
+
+**1. Discovery.** Partner reads `nikeinc.com/.well-known/brand.json`, finds the `agents[]` entry:
+
+```json
+{ "type": "brand", "url": "https://agent.nikeinc.com/mcp", "id": "nike_inc_brand_agent" }
+```
+
+**2. Capability check.** Partner calls `get_adcp_capabilities` on the agent. Response includes `verify_subsidiary_claim`, `verify_property`, `verify_trademark` in `supported_tasks`.
+
+**3. Subsidiary verification.** Partner discovered `converse.com` claiming `house_domain: nikeinc.com`. Calls `verify_subsidiary_claim`:
+
+```json
+{ "claimed_subsidiary_domain": "converse.com", "claimed_brand_id": "converse" }
+→
+{ "status": "owned", "brand_id": "converse" }
+```
+
+**4. Property verification.** A property `jordan.com` is in scope. Partner calls `verify_property`:
+
+```json
+{ "property": { "type": "website", "identifier": "jordan.com" }, "use_case": "advertising" }
+→
+{ "status": "owned", "relationship": "owned", "brand_id": "jordan", "use_case_authorization": { "advertising": true } }
+```
+
+**5. Trademark verification.** A creative includes "AIR JORDAN." Partner calls `verify_trademark`:
+
+```json
+{ "mark": "AIR JORDAN", "registry": "USPTO" }
+→
+{ "status": "owned", "matched_registration": {...}, "use_case_authorization": { "advertising": true, "merchandise_resale": false } }
+```
+
+**6. Decision.** Partner has authoritative confirmation across three dimensions. The crawl-based mutual-assertion path was never invoked. Cache for 24h per agent's `Cache-Control` headers; re-validate on next campaign cycle.
+
+## Out of scope
+
+Three categories the verification surface deliberately doesn't cover:
+
+- **Creative conflict / brand-safety scoring.** "Is this creative in conflict with our guidelines?" is its own design space — overlaps with creative protocol and brand-safety vendors. May land as a separate RFC.
+- **Competitive relationships.** "Is this brand competitive to us?" is **indefinitely deferred.** A brand declaring competitors machine-readably has different incentives than declaring its own assets (legal exposure, antitrust optics). The protocol won't ship a primitive that asks brands to publicly enumerate enemies.
+- **Cryptographic provenance** of agent-issued statements beyond transport-level signing. Today: rely on RFC 9421 and `adcp_use`-tagged keys. Cryptographic claim-chains (e.g., "this trademark assertion is signed by the registrar plus the brand") are out of scope.
+
+## Prior art and adjacent standards
+
+No direct equivalent to brand-side authoritative verification exists in IAB Tech Lab or TAG today. The closest analogs:
+
+- **ads.txt / sellers.json / app-ads.txt** — authority over *inventory*. This RFC's surface is authority over *identity*. Complementary, not duplicative.
+- **WHOIS / RDAP** — authority over *domain registration*. Different layer; doesn't carry brand-licensing or use-case authorization.
+- **Trademark registry crawls** (USPTO, EUIPO, etc.) — authority over *registration facts*. The differentiator here is licensee-side posture and authorized use cases, which registries can't speak to.
+
+Within AdCP, the [provenance verifier contract](https://github.com/adcontextprotocol/adcp/pull/3468) (seller-publishes / buyer-represents / seller-confirms) uses an adjacent construction for a different field family. The verification surface is closer to a query API than a publish-verify pair, but the trust shape (brand publishes its agent, partners verify against the brand's published JWKS) is the same.
+
+## Migration
+
+No migration burden. All three tools are additive — a brand-agent that doesn't implement them simply doesn't advertise them. Consumers that don't speak these tools fall back to the existing crawl + `contact.email` paths.
+
+The [Conformance](/docs/brand-protocol/brand-json#conformance) update is one SHOULD: "Where the named house publishes a brand-agent advertising `verify_subsidiary_claim` in `supported_tasks`, consumers SHOULD prefer the agent's response over crawl-based mutual-assertion inference."
+
+## Resolved decisions (formerly open questions)
+
+The following were open during draft and are now resolved as the spec contract:
+
+1. **`verify_property.use_case` field.** Kept. Mirrors the `get_brand_identity` `use_case` precedent.
+2. **`pending_review` queue position vs aggregate window.** Aggregate only (`expected_resolution_window_days`). Queue position leaks operational state. Additionally: `pending_review` MUST come with the window AND the agent MUST transition or flip to `unknown` past the window. Enforcement is agent-side, not spec-level; the safety net is consumer-side fallback to crawl on stale `pending_review` responses. See [`pending_review` aging and the crawl safety net](#pending_review-aging-and-the-crawl-safety-net).
+3. **Agent says `not_ours` while leaf publishes `house_domain` claim.** Normative: agent wins. See [Trust model § conflict resolution](#trust-model). UI guidance for leaf's recourse is a follow-up issue.
+4. **Rate-limiting.** Agent's call; spec sets the expectation of per-`{caller, tool, target}`. Agents SHOULD return `Retry-After` on limit and SHOULD prefer cached-prior-answer over `RATE_LIMITED` error.
+5. **Bulk variants** (`verify_subsidiary_claims`, plural). Deferred. Filed as follow-up — crawlers will demand it within 6 months; v1 ships single-target shape.
+
+## References
+
+- [#4505](https://github.com/adcontextprotocol/adcp/pull/4505) — landed distributed brand.json + the email-based self-healing SHOULD this RFC supersedes
+- [#4521](https://github.com/adcontextprotocol/adcp/issues/4521) — original "typed verification endpoint" issue that motivated this work
+- [`brand.json`](/docs/brand-protocol/brand-json) — normative spec for the underlying mutual-assertion trust model
+- [`building-a-brand-agent`](/docs/brand-protocol/building-a-brand-agent) — Tier 1 (`get_brand_identity`) reference; Tier 2 (this RFC) implementation guide is a planned addition
+- [`get_brand_identity`](/docs/brand-protocol/tasks/get_brand_identity) — the existing tool whose pattern these verifications follow
+- [`verify_subsidiary_claim`](/docs/brand-protocol/tasks/verify_subsidiary_claim) — task reference
+- [`verify_property`](/docs/brand-protocol/tasks/verify_property) — task reference
+- [`verify_trademark`](/docs/brand-protocol/tasks/verify_trademark) — task reference
diff --git a/docs/brand-protocol/tasks/verify_property.mdx b/docs/brand-protocol/tasks/verify_property.mdx
new file mode 100644
index 0000000000..bbc8acd33f
--- /dev/null
+++ b/docs/brand-protocol/tasks/verify_property.mdx
@@ -0,0 +1,169 @@
+---
+title: verify_property
+description: "verify_property is the AdCP brand-protocol task for asking a brand-agent whether a property — website, app, podcast, etc. — belongs to this brand. Returns an authoritative ownership status with the commercial relationship (owned, direct, delegated, ad_network) and optional use-case authorization."
+"og:title": "AdCP — verify_property"
+testable: true
+---
+
+
+**Proposed (RFC).** This task is part of the [brand verification RFC](/docs/brand-protocol/proposals/brand-verification-rfc) and not yet normative.
+
+
+Ask a brand-agent whether a property (website, app, podcast, etc.) belongs to this brand. **This is a prerequisite gate — check before you proceed, not a signal you consume after a decision is locked in.** Used at inventory onboarding, creative clearance, and fraud-escalation gates. Never at bid time (sub-100ms auction budgets don't accommodate MCP round-trips). Cache and re-validate at the next gate.
+
+## Schema
+
+- **Request**: [`verify-property-request.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-property-request.json)
+- **Response**: [`verify-property-response.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-property-response.json)
+
+## Capability discovery
+
+The brand-agent advertises this task in its `get_adcp_capabilities` response (alongside any other [verification tools](/docs/brand-protocol/proposals/brand-verification-rfc) it implements):
+
+```json
+{
+ "supported_protocols": ["brand"],
+ "supported_tasks": ["get_brand_identity", "verify_property"]
+}
+```
+
+## When to use
+
+Verification gates the *next step* in a workflow. Concrete gating points:
+
+- **Inventory onboarding.** An SSP adds a property to its catalog; verification gates the catalog entry before the property goes live for bidding.
+- **Creative clearance.** A creative-approval workflow encounters a property reference; verification gates approval.
+- **Fraud escalation.** A domain is suspected of falsely claiming brand affiliation; verification gates the chosen escalation path (block, flag, investigate).
+
+If the answer is "consumed informationally after the decision is already locked in," you're using the wrong tool — read `brand.json` `properties[]` directly. The gate-before-proceed semantics are what justify the MCP round-trip.
+
+Cache 24-72h for `owned`/`not_ours`; less for `transferring`. Re-check at the next gate, not at every bid.
+
+## Authorization tiers
+
+| Tier | What you get |
+|---|---|
+| **Public** | `status`, `relationship` (when owned/transferring), `brand_id`, `regions`, `context_note`. |
+| **Authorized** (via [`sync_accounts`](/docs/accounts/tasks/sync_accounts)) | Everything above, plus `use_case_authorization` — per-use-case permission flags. |
+
+The full tier table is in [the RFC](/docs/brand-protocol/proposals/brand-verification-rfc#authorization-tiers).
+
+## Quick start
+
+
+```json Request (verify a regional domain)
+{
+ "property": {
+ "type": "website",
+ "identifier": "nike.cn",
+ "region": "CN"
+ }
+}
+```
+
+```json Response (owned)
+{
+ "status": "owned",
+ "relationship": "owned",
+ "brand_id": "nike",
+ "regions": ["CN"],
+ "context_note": "Regional site for China market"
+}
+```
+
+```json Request (verify an app bundle with use case)
+{
+ "property": {
+ "type": "mobile_app",
+ "identifier": "com.nike.snkrs",
+ "store": "apple"
+ },
+ "use_case": "advertising"
+}
+```
+
+```json Response (owned, authorized caller)
+{
+ "status": "owned",
+ "relationship": "owned",
+ "brand_id": "jordan",
+ "use_case_authorization": {
+ "advertising": true,
+ "endorsement": false
+ }
+}
+```
+
+```json Response (transferring)
+{
+ "status": "transferring",
+ "relationship": "owned",
+ "brand_id": "nike",
+ "context_note": "Domain transferring to subsidiary on 2026-09-01; treat as owned."
+}
+```
+
+```json Response (disputed)
+{
+ "status": "disputed",
+ "context_note": "Domain owned by unaffiliated third party; we do not authorize use of our marks on it."
+}
+```
+
+```json Response (not ours)
+{
+ "status": "not_ours"
+}
+```
+
+```json Response (error)
+{
+ "errors": [
+ {
+ "code": "INVALID_INPUT",
+ "message": "property.identifier must be a valid hostname or app bundle id."
+ }
+ ]
+}
+```
+
+
+## Relationship and use case
+
+The `relationship` field — present when status is `owned` or `transferring` — answers "the brand controls this property, but commercially how?":
+
+- `owned` — brand owns and operates the property directly.
+- `direct` — brand is the direct sales path even if a third party runs the tech.
+- `delegated` — brand has delegated monetization (e.g., Mediavine for a food blog).
+- `ad_network` — brand sells as part of a network/exchange.
+
+The `use_case` request field — and the `use_case_authorization` response — handle the orthogonal question of "is the named use case allowed?" A brand may own a domain but not authorize ad-network resale on it. The registered use-case keys are `advertising`, `endorsement`, `retail_listing`, `editorial`, `commercial_advertising`, `merchandise_resale`. Agents MAY add additional keys.
+
+## Trust model
+
+The agent's response is **authoritative** when signed under the brand's `adcp_use: "response-signing"` JWK. Consumers SHOULD prefer the agent's response over `brand.json` `properties[]` inference. Conflict resolution mirrors [`verify_subsidiary_claim` § Trust model](/docs/brand-protocol/tasks/verify_subsidiary_claim#trust-model):
+
+| Agent says | Consumer treats as |
+|---|---|
+| `owned` | Trusted; bid/curate against `properties[]` confirmation. |
+| `transferring` | Trusted; MAY treat as owned for stability; surface in UI. |
+| `disputed` | Brand rejects this property claim. Do not extend trust. |
+| `not_ours` | Brand affirms no relationship. |
+| `unknown` | Fall back to `brand.json` `properties[]` inference. |
+
+When the agent answers, the agent wins.
+
+## Caching
+
+- `owned` / `not_ours` / `disputed` — stable. 24-72h cache.
+- `transferring` — volatile until transition. Max-age ≤4h.
+- `use_case_authorization` — most volatile. Re-check per session.
+- `unknown` — short cache (≤1h).
+
+## Error handling
+
+| Error code | Cause |
+|---|---|
+| `AUTH_INVALID` | Caller's signed envelope did not verify. |
+| `RATE_LIMITED` | Agent has rate-limited the caller. Agents SHOULD return `Retry-After` and prefer cached prior answer over hard error. |
+| `INVALID_INPUT` | `property.identifier` is not valid for the declared `type`. |
diff --git a/docs/brand-protocol/tasks/verify_subsidiary_claim.mdx b/docs/brand-protocol/tasks/verify_subsidiary_claim.mdx
new file mode 100644
index 0000000000..6d0890320a
--- /dev/null
+++ b/docs/brand-protocol/tasks/verify_subsidiary_claim.mdx
@@ -0,0 +1,162 @@
+---
+title: verify_subsidiary_claim
+description: "verify_subsidiary_claim is the AdCP brand-protocol task for asking a brand-agent whether a named brand is a subsidiary of this house. Returns an authoritative status — owned, pending_review, transferring, disputed, not_ours, or unknown — used in place of crawl-based mutual-assertion inference when the brand-agent supports it."
+"og:title": "AdCP — verify_subsidiary_claim"
+testable: true
+---
+
+
+**Proposed (RFC).** This task is part of the [brand verification RFC](/docs/brand-protocol/proposals/brand-verification-rfc) and not yet normative. The crawl-based [mutual-assertion model](/docs/brand-protocol/brand-json#mutual-assertion-trust-model) remains authoritative until the RFC ratifies.
+
+
+Ask a brand-agent whether the named brand is a subsidiary of this house. **This is a prerequisite gate — check before you proceed.** Returns an authoritative status from the brand's own agent, replacing crawl-based inference when the brand-agent supports this task.
+
+## Schema
+
+- **Request**: [`verify-subsidiary-claim-request.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-subsidiary-claim-request.json)
+- **Response**: [`verify-subsidiary-claim-response.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-subsidiary-claim-response.json)
+
+## Capability discovery
+
+The brand-agent advertises this task in its `get_adcp_capabilities` response:
+
+```json
+{
+ "supported_protocols": ["brand"],
+ "supported_tasks": [
+ "get_brand_identity",
+ "verify_subsidiary_claim"
+ ]
+}
+```
+
+Callers MUST check capability advertisement before relying on this task; brand-agents may implement any subset of the [verification surface](/docs/brand-protocol/proposals/brand-verification-rfc).
+
+## When to use
+
+Verification gates the *next step* in a workflow. Call this where you would otherwise extend governance trust through a brand → house relationship: inventory onboarding, brand-relationship establishment, member-feature provisioning, creative clearance that depends on parent-brand authorization. Check the claim with the named house, act on the answer — don't reconcile state after the fact.
+
+The crawl-based mutual-assertion model — leaf publishes `house_domain: X`, X's `brand_refs[]` includes leaf, both halves verified by independent fetch — covers the common case but has gaps this tool closes:
+
+- **Two-state visibility.** Crawl answers "mutual" or "not mutual." It can't surface *pending review*, *transferring*, or *disputed*.
+- **TTL-bound freshness.** A consumer's view is only as fresh as its last crawl of both sides.
+- **One-sided pessimism.** A leaf-only edge gets downgraded to "claimed, unverified" even when the brand intends to confirm.
+
+`verify_subsidiary_claim` asks the brand directly. A signed `owned` from the brand's own agent is real-time-authoritative — stronger than a published `brand_refs[]` entry. A signed `not_ours` overrides a leaf's `house_domain` claim. And `pending_review` / `transferring` surface states the crawl model simply can't.
+
+## Authorization tiers
+
+Verification tools follow the same public/authorized split as `get_brand_identity`. The authoritative tier table lives in [the RFC](/docs/brand-protocol/proposals/brand-verification-rfc#authorization-tiers); the short version for this tool:
+
+| Tier | What you get |
+|---|---|
+| **Public** | `status`, public `brand_id` (when applicable), `context_note` (when populated by the brand — carries dispute rationale, transfer details, etc). |
+| **Authorized** (via [`sync_accounts`](/docs/accounts/tasks/sync_accounts)) | Everything above, plus `first_observed_by_house_at` and `expected_resolution_window_days`. |
+
+Queue position, internal ticket state, and team routing are never exposed.
+
+## Quick start
+
+
+```json Request
+{
+ "claimed_subsidiary_domain": "converse.com",
+ "claimed_brand_id": "converse",
+ "observed_at": "2026-05-14T10:00:00Z"
+}
+```
+
+```json Response (owned)
+{
+ "status": "owned",
+ "brand_id": "converse"
+}
+```
+
+```json Response (pending, authorized)
+{
+ "status": "pending_review",
+ "brand_id": "converse",
+ "first_observed_by_house_at": "2026-05-12T14:00:00Z",
+ "expected_resolution_window_days": 7
+}
+```
+
+```json Response (transferring)
+{
+ "status": "transferring",
+ "brand_id": "converse",
+ "context_note": "Pending divestiture; closes 2026-08-15. Treat as owned for stability."
+}
+```
+
+```json Response (disputed)
+{
+ "status": "disputed",
+ "context_note": "We do not own this brand; trademark held by separate entity."
+}
+```
+
+```json Response (not ours)
+{
+ "status": "not_ours"
+}
+```
+
+```json Response (unknown)
+{
+ "status": "unknown"
+}
+```
+
+```json Response (error)
+{
+ "errors": [
+ {
+ "code": "AUTH_INVALID",
+ "message": "Signed envelope did not verify against any published JWK."
+ }
+ ]
+}
+```
+
+
+## Trust model
+
+The agent's response is **authoritative** when signed under the brand's `adcp_use: "response-signing"` JWK (the AdCP webhook-signing convention applies to response-side signing too; the key purpose is distinct from `request-signing`). Consumers SHOULD prefer the agent's response over crawl-based mutual-assertion inference when the brand-agent advertises `verify_subsidiary_claim` in its `supported_tasks`.
+
+Conflict resolution between agent response and crawl observation (normative — see [RFC § Trust model](/docs/brand-protocol/proposals/brand-verification-rfc#trust-model)):
+
+| Agent says | Crawl says | Consumer treats as |
+|---|---|---|
+| `owned` | Mutual or leaf-only | Trusted edge. Governance propagation: yes. |
+| `pending_review` | Leaf-only (parent silent) | Trusted leaf identity; relationship trust withheld. When the response's `expected_resolution_window_days` is exceeded without transition, consumer SHOULD treat as if `unknown` returned and fall back to crawl. UI: "Pending parent confirmation, expected within N days." |
+| `transferring` | Any | Trusted but in-flight; consumers MAY treat as `owned` for stability and SHOULD surface the transition in UI. |
+| `disputed` | Leaf-only or mutual | The brand has spoken. Treat leaf as having a rejected parent claim. UI: "Brand X says this is not theirs." |
+| `not_ours` | Leaf-only | Leaf treated as standalone with disputed parent claim. |
+| `unknown` | Any | Fall back to crawl-based mutual-assertion inference. |
+
+When the agent answers, the agent wins. The crawl path remains the fallback only when the agent is unreachable, returns `unknown`, or doesn't implement the task.
+
+`pending_review` requires the agent to set `expected_resolution_window_days` AND transition the claim to a terminal status (or `unknown`) once the window elapses. **Enforcement is agent-side, not spec-level** — most day-one brand deployments won't have the automation to auto-flip stale claims. The safety net is consumer-side: when a `pending_review` response is older than its declared window, consumers SHOULD treat the answer as effectively `unknown` and fall back to crawl-based inference (same fallback used when the agent is unreachable). The aging contract is a declared intent with a defined fallback, not an externally-enforced guarantee. See [RFC § pending_review aging](/docs/brand-protocol/proposals/brand-verification-rfc#pending_review-aging-and-the-crawl-safety-net).
+
+## Caching
+
+Verification responses SHOULD be cacheable per standard HTTP semantics. Agents SHOULD set `Cache-Control: max-age=N`:
+
+- `owned` / `not_ours` / `disputed` — stable. 24-72h is reasonable.
+- `pending_review` — volatile. Re-check per governance decision; max-age ≤1h.
+- `transferring` — volatile until transition. Max-age ≤4h.
+- `unknown` — short cache (≤1h).
+
+Consumers MAY override agent-supplied cache hints downward but SHOULD NOT cache beyond the agent's `max-age`.
+
+## Error handling
+
+The `VerifySubsidiaryClaimError` arm returns on transport/auth/input failures. Operational failures (agent's internal database is down, etc.) MAY return `status: "unknown"` instead of an error, so the caller can fall back to crawl.
+
+| Error code | Cause |
+|---|---|
+| `AUTH_INVALID` | Caller's signed envelope did not verify. (Distinct from `AUTH_MISSING`: an unauthenticated public call returns the public success arm with `status`, not an error.) |
+| `RATE_LIMITED` | Agent has rate-limited the caller. Agents SHOULD return `Retry-After` and SHOULD prefer returning a cached prior answer over a hard error. |
+| `INVALID_INPUT` | `claimed_subsidiary_domain` is not a valid hostname or fails the agent's input policy. |
diff --git a/docs/brand-protocol/tasks/verify_trademark.mdx b/docs/brand-protocol/tasks/verify_trademark.mdx
new file mode 100644
index 0000000000..0b799fd517
--- /dev/null
+++ b/docs/brand-protocol/tasks/verify_trademark.mdx
@@ -0,0 +1,176 @@
+---
+title: verify_trademark
+description: "verify_trademark is the AdCP brand-protocol task for asking a brand-agent whether a trademark is owned, licensed in, or licensed out by this brand. The differentiator vs registry crawls: authoritative licensee posture and per-use-case authorization that registries cannot provide."
+"og:title": "AdCP — verify_trademark"
+testable: true
+---
+
+
+**Proposed (RFC).** This task is part of the [brand verification RFC](/docs/brand-protocol/proposals/brand-verification-rfc) and not yet normative.
+
+
+Ask a brand-agent whether a trademark is owned, licensed in, or licensed out by this brand. The differentiator from registry crawls: registries can tell you a mark exists, who registered it, and the registration status — they can't tell you which use cases the brand authorizes, who the licensor is on a licensed-in mark, or whether the mark is mid-transfer. This tool fills that gap.
+
+## Schema
+
+- **Request**: [`verify-trademark-request.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-trademark-request.json)
+- **Response**: [`verify-trademark-response.json`](https://adcontextprotocol.org/schemas/v3/brand/verify-trademark-response.json)
+
+## Capability discovery
+
+The brand-agent advertises this task in its `get_adcp_capabilities` response:
+
+```json
+{
+ "supported_protocols": ["brand"],
+ "supported_tasks": ["get_brand_identity", "verify_trademark"]
+}
+```
+
+## When to use
+
+**This is a creative-clearance gate.** Call it where a workflow about to apply a trademark to a creative needs the brand's authoritative go/no-go — not after the creative is already approved. Trademark ownership has gnarly edges that static publication and registry crawls can't always resolve:
+
+- **Cross-jurisdiction conflicts.** USPTO `CONVERSE` and EUIPO `CONVERSE` may be held by different parties.
+- **Nice class disambiguation.** `DELTA` (airline, class 39) and `DELTA` (faucet, class 11) are different brands with the same mark.
+- **Licensing chains.** A brand uses a mark under license (`licensed_in`) or licenses it out (`licensed_out`) — neither is captured cleanly by static publication or registries.
+- **Authorized use cases.** Whether the brand permits a mark for advertising vs editorial vs merchandise is brand-policy, not registry-fact.
+
+`verify_trademark` lets a consumer ask the brand's authoritative agent for a definitive answer, including the matched registration, licensing relationship, and per-use-case authorization.
+
+## Authorization tiers
+
+| Tier | What you get |
+|---|---|
+| **Public** | `status`, `matched_registration`, `licensor_domain` (when `licensed_in`), `countries`, `nice_classes`, `context_note`. |
+| **Authorized** (via [`sync_accounts`](/docs/accounts/tasks/sync_accounts)) | Everything above, plus `use_case_authorization` — the highest-value field, derivable from no registry crawl. |
+
+Trademark facts are largely public-record. The authorized tier exposes brand-side policy that registries can't speak to. The full tier table is in [the RFC](/docs/brand-protocol/proposals/brand-verification-rfc#authorization-tiers).
+
+## Quick start
+
+
+```json Request (verify by registry number)
+{
+ "mark": "AIR JORDAN",
+ "registry": "USPTO",
+ "number": "1234567"
+}
+```
+
+```json Response (owned)
+{
+ "status": "owned",
+ "matched_registration": {
+ "registry": "USPTO",
+ "number": "1234567",
+ "mark": "AIR JORDAN",
+ "registration_status": "active"
+ },
+ "countries": ["US"],
+ "nice_classes": [25, 41]
+}
+```
+
+```json Request (verify by mark + region)
+{
+ "mark": "CONVERSE",
+ "countries": ["FR", "DE"]
+}
+```
+
+```json Response (licensed_in)
+{
+ "status": "licensed_in",
+ "matched_registration": {
+ "registry": "EUIPO",
+ "number": "EU98765",
+ "mark": "CONVERSE",
+ "registration_status": "active"
+ },
+ "licensor_domain": "converseholdings-eu.com",
+ "countries": ["FR", "DE", "IT", "ES"],
+ "nice_classes": [25]
+}
+```
+
+```json Response (authorized, with use cases)
+{
+ "status": "owned",
+ "matched_registration": { "registry": "USPTO", "number": "9999999", "mark": "SWOOSH", "registration_status": "active" },
+ "countries": ["US"],
+ "nice_classes": [25, 41],
+ "use_case_authorization": {
+ "editorial": true,
+ "commercial_advertising": false,
+ "merchandise_resale": false
+ }
+}
+```
+
+```json Response (disputed)
+{
+ "status": "disputed",
+ "context_note": "Mark in this jurisdiction held by separate entity; we contest their registration."
+}
+```
+
+```json Response (not ours)
+{
+ "status": "not_ours"
+}
+```
+
+```json Response (transferring)
+{
+ "status": "transferring",
+ "matched_registration": { "registry": "USPTO", "number": "1234567", "mark": "AIR JORDAN", "registration_status": "active" },
+ "context_note": "Mark transferring to spinoff entity on 2026-08-15."
+}
+```
+
+```json Response (error)
+{
+ "errors": [
+ {
+ "code": "AMBIGUOUS_MATCH",
+ "message": "Multiple registrations match 'CONVERSE'; narrow with registry, number, or countries."
+ }
+ ]
+}
+```
+
+
+## Status semantics for trademarks
+
+| Status | Meaning |
+|---|---|
+| `owned` | The brand holds the registration. |
+| `licensed_in` | The brand uses the mark under license (`licensor_domain` populated). |
+| `licensed_out` | The brand licenses the mark to another entity. |
+| `transferring` | Mark transferring to another party (M&A, licensing transition, spinoff). |
+| `disputed` | The brand actively contests this registration. |
+| `not_ours` | The brand has no relationship to this registration. |
+| `unknown` | Agent cannot match the input. Caller MAY fall back to registry crawl. |
+
+`pending_review` is not applicable — trademark registrations are public-record events with definitive ownership at any given time.
+
+## Trust model
+
+The agent's response is **authoritative** when signed under the brand's `adcp_use: "response-signing"` JWK. Conflict resolution mirrors [`verify_subsidiary_claim` § Trust model](/docs/brand-protocol/tasks/verify_subsidiary_claim#trust-model). When the agent and the registry disagree (rare — e.g., a mark recently transferred but not yet reflected in the registry's public records), the agent wins because the brand is the authority over its current licensing relationships.
+
+## Caching
+
+- `owned` / `not_ours` / `disputed` — stable. 24-72h cache.
+- `licensed_in` / `licensed_out` — moderately volatile. 24h cache; re-check on contract-affected workflows.
+- `transferring` — volatile until transition. Max-age ≤4h.
+- `use_case_authorization` — most volatile. Re-check per session.
+
+## Error handling
+
+| Error code | Cause |
+|---|---|
+| `AUTH_INVALID` | Caller's signed envelope did not verify. |
+| `RATE_LIMITED` | Agent has rate-limited the caller. Agents SHOULD return `Retry-After` and prefer cached prior answer over hard error. |
+| `INVALID_INPUT` | Required field missing or fails the agent's input policy. |
+| `AMBIGUOUS_MATCH` | Multiple registrations match the input (typically when neither `registry` nor `number` is provided and the mark exists in several jurisdictions). Caller should narrow with `registry`, `number`, or `countries`. |
diff --git a/scripts/oneof-discriminators.baseline.json b/scripts/oneof-discriminators.baseline.json
index 146893d7cb..a0da62bde2 100644
--- a/scripts/oneof-discriminators.baseline.json
+++ b/scripts/oneof-discriminators.baseline.json
@@ -38,8 +38,8 @@
},
"brand.json##/oneOf": {
"kind": "dangerous",
- "variants": 4,
- "note": "shared-required=[∅]; 0:req=[authoritative_location] | 1:req=[house] | 2:req=[∅] | 3:req=[house,brands]"
+ "variants": 5,
+ "note": "shared-required=[∅]; 0:req=[authoritative_location] | 1:req=[house] | 2:req=[∅] | 3:req=[house] | 4:req=[∅]"
},
"brand/acquire-rights-response.json##/oneOf": {
"kind": "dangerous",
@@ -76,6 +76,21 @@
"variants": 2,
"note": "0:[rights_id,terms] | 1:[errors]"
},
+ "brand/verify-property-response.json##/oneOf": {
+ "kind": "narrowable",
+ "variants": 2,
+ "note": "0:[status] | 1:[errors]"
+ },
+ "brand/verify-subsidiary-claim-response.json##/oneOf": {
+ "kind": "narrowable",
+ "variants": 2,
+ "note": "0:[status] | 1:[errors]"
+ },
+ "brand/verify-trademark-response.json##/oneOf": {
+ "kind": "narrowable",
+ "variants": 2,
+ "note": "0:[status] | 1:[errors]"
+ },
"compliance/comply-test-controller-response.json##/oneOf": {
"kind": "dangerous",
"variants": 7,
diff --git a/static/schemas/source/brand/verification-status.json b/static/schemas/source/brand/verification-status.json
new file mode 100644
index 0000000000..51160621b6
--- /dev/null
+++ b/static/schemas/source/brand/verification-status.json
@@ -0,0 +1,27 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verification-status.json",
+ "title": "Verification Status",
+ "description": "Status returned by brand-agent verification tools (verify_subsidiary_claim, verify_property, verify_trademark). Captures the rich state surface crawl-based mutual-assertion cannot express: pending review, disputed, licensed in/out, and explicit not-ours / unknown answers. Not every tool returns every status — see each tool's response schema for the applicable subset.",
+ "type": "string",
+ "enum": [
+ "owned",
+ "pending_review",
+ "transferring",
+ "disputed",
+ "not_ours",
+ "licensed_in",
+ "licensed_out",
+ "unknown"
+ ],
+ "enumDescriptions": {
+ "owned": "Definitively belongs to this brand, currently.",
+ "pending_review": "The brand is aware of this claim and has not yet decided. When returning this status, the agent MUST also return `expected_resolution_window_days` and MUST transition the claim to a terminal status (owned/disputed/not_ours) or flip to `unknown` once the window elapses. Consumers SHOULD NOT extend governance trust through pending claims.",
+ "transferring": "Ownership is provably changing — M&A in flight, divestiture closing, or a known imminent transition. Distinct from `pending_review` (under-review-by-us); `transferring` signals 'the answer is known to be becoming something else.' Consumers SHOULD treat as `owned` for stability until the agent moves to the new state, but MAY surface the in-flight state in UIs.",
+ "disputed": "The brand actively rejects this claim. Consumers MUST treat the claim as invalid and SHOULD surface the dispute (e.g., 'X says this is not theirs').",
+ "not_ours": "The brand affirms it is not their property / subsidiary / mark. Equivalent to 'disputed' but used when the brand-agent has no record of an existing claim — a clean 'we do not own this.'",
+ "licensed_in": "The brand uses this asset under license from another entity. The response carries `licensor_domain` when this status is returned. Applies to trademarks and (rarely) properties.",
+ "licensed_out": "The brand licenses this asset to another entity. Applies to trademarks; rare for properties or subsidiaries.",
+ "unknown": "The agent has no position. Caller MAY fall back to crawl-based mutual-assertion inference. Returned when the agent cannot classify the input (insufficient context, unrecognized identifier, out-of-scope query)."
+ }
+}
diff --git a/static/schemas/source/brand/verify-property-request.json b/static/schemas/source/brand/verify-property-request.json
new file mode 100644
index 0000000000..d16be7cd31
--- /dev/null
+++ b/static/schemas/source/brand/verify-property-request.json
@@ -0,0 +1,47 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-property-request.json",
+ "title": "Verify Property Request",
+ "description": "Ask a brand-agent whether a property (website, app, podcast, etc.) belongs to this brand. Used by DSPs verifying inventory ownership, fraud detectors confirming domain claims, or any consumer that wants the brand's authoritative answer rather than inferring from a crawl of properties[]. Read-only; naturally idempotent.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "properties": {
+ "property": {
+ "type": "object",
+ "description": "The property whose ownership is being verified. Shape matches brand.json's properties[] entry.",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["website", "mobile_app", "ctv_app", "desktop_app", "dooh", "podcast", "radio", "streaming_audio"],
+ "description": "Property type, matching the brand.json properties[] enum."
+ },
+ "identifier": {
+ "type": "string",
+ "description": "Property identifier — domain for website/podcast/radio, bundle id for apps, etc."
+ },
+ "store": {
+ "type": "string",
+ "enum": ["apple", "google", "amazon", "roku", "fire_tv", "samsung", "lg", "vizio", "other"],
+ "description": "App store, when property type is an app."
+ },
+ "region": {
+ "type": "string",
+ "description": "ISO 3166-1 alpha-2 country code or 'global'."
+ }
+ },
+ "required": ["type", "identifier"],
+ "additionalProperties": true
+ },
+ "use_case": {
+ "type": "string",
+ "description": "Optional caller-declared use case (e.g., 'advertising', 'endorsement', 'retail_listing'). The agent MAY scope its answer to this use case — e.g., a brand may own a domain but not authorize ad-network resale on it.",
+ "maxLength": 100
+ }
+ },
+ "required": ["property"],
+ "additionalProperties": true
+}
diff --git a/static/schemas/source/brand/verify-property-response.json b/static/schemas/source/brand/verify-property-response.json
new file mode 100644
index 0000000000..4bba6985fc
--- /dev/null
+++ b/static/schemas/source/brand/verify-property-response.json
@@ -0,0 +1,96 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-property-response.json",
+ "title": "Verify Property Response",
+ "description": "The brand-agent's authoritative answer on property ownership. Mirrors the property-relationship enum used in brand.json properties[].",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "oneOf": [
+ {
+ "title": "VerifyPropertySuccess",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": ["owned", "transferring", "disputed", "not_ours", "unknown"],
+ "description": "Verification status. Subset of /schemas/brand/verification-status.json applicable to property claims. `pending_review` is excluded because property ownership is generally definitive — agents that need to surface in-process state SHOULD use `transferring`. `licensed_in` / `licensed_out` are excluded because licensing applies to trademarks and brands, not the underlying digital property."
+ },
+ "relationship": {
+ "type": "string",
+ "enum": ["owned", "direct", "delegated", "ad_network"],
+ "description": "Public — when status is `owned` or `transferring`, the commercial relationship between the brand and the property. Mirrors brand.json's properties[].relationship enum. Omitted when status is not in that set."
+ },
+ "brand_id": {
+ "type": "string",
+ "description": "Public — the brand_id within the house this property belongs to. Lets the caller cross-reference with brand.json data.",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "regions": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Public — ISO 3166-1 alpha-2 country codes where this property is the brand's primary/canonical surface. Use the sentinel `\"global\"` (matching the request schema's region field) to signal no regional restriction. Omitting the field is equivalent to `unknown regions`."
+ },
+ "use_case_authorization": {
+ "type": "object",
+ "description": "Authorized-tier only. Per-use-case authorization flags. The agent's answer to whether the named use case is permitted on this property — distinct from ownership. Registered keys: advertising, endorsement, retail_listing, editorial, commercial_advertising, merchandise_resale. Agents MAY add additional keys (additionalProperties: boolean).",
+ "properties": {
+ "advertising": { "type": "boolean", "description": "Programmatic advertising allowed via AdCP." },
+ "endorsement": { "type": "boolean", "description": "Use of the property in endorsement deals." },
+ "retail_listing": { "type": "boolean", "description": "Listing in retail / commerce contexts." },
+ "editorial": { "type": "boolean", "description": "Editorial mentions and reviews." },
+ "commercial_advertising": { "type": "boolean", "description": "Paid advertising placements off-AdCP." },
+ "merchandise_resale": { "type": "boolean", "description": "Resale of merchandise associated with the property." }
+ },
+ "additionalProperties": { "type": "boolean" }
+ },
+ "context_note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "Public — free-text context the brand chooses to surface (e.g., 'Regional CN site', 'Legacy domain, redirects to nike.com'). When status is `disputed`, carries the rationale."
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["status"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["errors"] }
+ ]
+ }
+ },
+ {
+ "title": "VerifyPropertyError",
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "/schemas/core/error.json"
+ },
+ "minItems": 1
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["errors"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["status"] }
+ ]
+ }
+ }
+ ],
+ "properties": {}
+}
diff --git a/static/schemas/source/brand/verify-subsidiary-claim-request.json b/static/schemas/source/brand/verify-subsidiary-claim-request.json
new file mode 100644
index 0000000000..5503dc1c96
--- /dev/null
+++ b/static/schemas/source/brand/verify-subsidiary-claim-request.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-subsidiary-claim-request.json",
+ "title": "Verify Subsidiary Claim Request",
+ "description": "Ask a brand-agent whether the named brand is a subsidiary of this house. Used by consumers (DSPs, crawlers, partner agents) that have detected a leaf claiming `house_domain` pointing at this brand and want an authoritative answer before extending governance trust. The crawl-based mutual-assertion path remains the fallback when the brand-agent doesn't implement this task or returns `unknown`. Read-only; naturally idempotent — the brand's internal claim-bookkeeping deduplicates on (caller_identity, claimed_subsidiary_domain).",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "properties": {
+ "claimed_subsidiary_domain": {
+ "type": "string",
+ "description": "The domain of the leaf brand whose `house_domain` claim is being verified. Typically the leaf's canonical brand.json is hosted at this domain (or via authoritative_location indirection).",
+ "format": "hostname"
+ },
+ "claimed_brand_id": {
+ "type": "string",
+ "description": "Stable brand identifier the leaf uses for itself. Optional but recommended — lets the agent disambiguate when multiple brands share a domain (rare but legal).",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "observed_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the caller observed the leaf's claim. Helps the agent age claims and prioritize fresh ones in its internal queue."
+ }
+ },
+ "required": ["claimed_subsidiary_domain"],
+ "additionalProperties": true
+}
diff --git a/static/schemas/source/brand/verify-subsidiary-claim-response.json b/static/schemas/source/brand/verify-subsidiary-claim-response.json
new file mode 100644
index 0000000000..6b13267787
--- /dev/null
+++ b/static/schemas/source/brand/verify-subsidiary-claim-response.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-subsidiary-claim-response.json",
+ "title": "Verify Subsidiary Claim Response",
+ "description": "The brand-agent's authoritative answer to whether the claimed brand is a subsidiary of this house. Always returns a `status`; richer fields are gated by caller authorization.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "oneOf": [
+ {
+ "title": "VerifySubsidiaryClaimSuccess",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": ["owned", "pending_review", "transferring", "disputed", "not_ours", "unknown"],
+ "description": "Verification status. Subset of /schemas/brand/verification-status.json applicable to subsidiary claims; `licensed_in` / `licensed_out` are excluded because subsidiaries aren't licensed (brands and trademarks are)."
+ },
+ "brand_id": {
+ "type": "string",
+ "description": "Public — the house's brand_id for this subsidiary when status is `owned`, `pending_review`, or `transferring`. Lets the caller cross-reference with brand_refs[] / brands[] entries.",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "first_observed_by_house_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Authorized-tier only. When the house first became aware of this claim (whether via this RPC, an inbound notification, or its own discovery)."
+ },
+ "expected_resolution_window_days": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Authorized-tier only when populated alongside a terminal status. REQUIRED when status is `pending_review`. The house's expected window for resolving a `pending_review` claim. Agents MUST transition the claim to a terminal status or to `unknown` once the window elapses — staleness past the window is a contract violation. Aggregate signal; does not expose queue position or internal ops."
+ },
+ "context_note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "Public — free-text context the brand chooses to surface. When status is `disputed`, this carries the rationale (e.g., 'Trademark conflict; not affiliated'). When status is `transferring`, the in-flight transition's nature (e.g., 'Divestiture closing 2026-08'). May be empty."
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["status"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["errors"] }
+ ]
+ }
+ },
+ {
+ "title": "VerifySubsidiaryClaimError",
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "/schemas/core/error.json"
+ },
+ "minItems": 1
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["errors"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["status"] }
+ ]
+ }
+ }
+ ],
+ "properties": {}
+}
diff --git a/static/schemas/source/brand/verify-trademark-request.json b/static/schemas/source/brand/verify-trademark-request.json
new file mode 100644
index 0000000000..d922633a61
--- /dev/null
+++ b/static/schemas/source/brand/verify-trademark-request.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-trademark-request.json",
+ "title": "Verify Trademark Request",
+ "description": "Ask a brand-agent whether a trademark is owned, licensed in, or licensed out by this brand. Used in creative approval workflows, brand-safety pipelines, and any consumer that needs the brand's authoritative ownership statement over a mark. Read-only; naturally idempotent.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "properties": {
+ "mark": {
+ "type": "string",
+ "description": "The registered mark as published (e.g., 'AIR JORDAN', 'NIKE', 'SWOOSH'). At minimum, callers must provide the mark text; registry and number narrow the query when multiple registrations exist.",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "registry": {
+ "type": "string",
+ "description": "Trademark registry (e.g., 'USPTO', 'EUIPO', 'JPO', 'CNIPA'). Strongly recommended when the same mark is registered separately by different parties across jurisdictions.",
+ "maxLength": 50
+ },
+ "number": {
+ "type": "string",
+ "description": "Registration number as issued by the registry. Most precise narrowing key; agents will use this when present.",
+ "maxLength": 100
+ },
+ "countries": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 2, "maxLength": 2 },
+ "description": "Optional ISO 3166-1 alpha-2 country codes scoping the query. If a brand owns a mark in some jurisdictions and not others, this lets the caller ask about the specific region."
+ },
+ "use_case": {
+ "type": "string",
+ "description": "Optional caller-declared use case. The agent MAY scope its answer (e.g., a brand may permit editorial use of a mark but not commercial use).",
+ "maxLength": 100
+ }
+ },
+ "required": ["mark"],
+ "additionalProperties": true
+}
diff --git a/static/schemas/source/brand/verify-trademark-response.json b/static/schemas/source/brand/verify-trademark-response.json
new file mode 100644
index 0000000000..49215e10ec
--- /dev/null
+++ b/static/schemas/source/brand/verify-trademark-response.json
@@ -0,0 +1,111 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "/schemas/brand/verify-trademark-response.json",
+ "title": "Verify Trademark Response",
+ "description": "The brand-agent's authoritative answer on trademark ownership and licensing. Mirrors the shape of brand.json's #/definitions/trademark where status is `owned`, `licensed_in`, or `licensed_out`.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "/schemas/core/version-envelope.json"
+ }
+ ],
+ "oneOf": [
+ {
+ "title": "VerifyTrademarkSuccess",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": ["owned", "licensed_in", "licensed_out", "transferring", "disputed", "not_ours", "unknown"],
+ "description": "Verification status. Subset of /schemas/brand/verification-status.json applicable to trademark claims. `pending_review` is excluded — trademark registrations are public-record events with definitive ownership at any given time; mid-transition cases use `transferring`."
+ },
+ "matched_registration": {
+ "type": "object",
+ "description": "Public — when status is `owned`, `licensed_in`, `licensed_out`, or `transferring`, the registration the agent matched the query to. Same shape as brand.json's #/definitions/trademark.",
+ "properties": {
+ "registry": { "type": "string" },
+ "number": { "type": "string" },
+ "mark": { "type": "string" },
+ "registration_status": {
+ "type": "string",
+ "enum": ["active", "pending", "abandoned", "cancelled", "expired"],
+ "description": "Registration status from the registry, distinct from this response's outer `status` field. Renamed from `status` on the brand.json definition to avoid collision with the verification status above."
+ }
+ },
+ "additionalProperties": true
+ },
+ "licensor_domain": {
+ "type": "string",
+ "format": "hostname",
+ "description": "Public — when status is `licensed_in`, the domain of the entity licensing this mark to the brand."
+ },
+ "countries": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 2, "maxLength": 2 },
+ "description": "Public — ISO 3166-1 alpha-2 country codes the response covers."
+ },
+ "nice_classes": {
+ "type": "array",
+ "items": { "type": "integer", "minimum": 1, "maximum": 45 },
+ "description": "Public — Nice Classification class numbers covered. Disambiguates cross-industry marks (Delta-airline class 39 vs Delta-faucet class 11)."
+ },
+ "use_case_authorization": {
+ "type": "object",
+ "description": "Authorized-tier only. Per-use-case permission for this mark. Registered keys: advertising, endorsement, retail_listing, editorial, commercial_advertising, merchandise_resale. Agents MAY add additional keys (additionalProperties: boolean).",
+ "properties": {
+ "advertising": { "type": "boolean" },
+ "endorsement": { "type": "boolean" },
+ "retail_listing": { "type": "boolean" },
+ "editorial": { "type": "boolean" },
+ "commercial_advertising": { "type": "boolean" },
+ "merchandise_resale": { "type": "boolean" }
+ },
+ "additionalProperties": { "type": "boolean" }
+ },
+ "context_note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "Public — free-text context the brand chooses to surface. When status is `disputed`, carries the rationale."
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["status"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["errors"] }
+ ]
+ }
+ },
+ {
+ "title": "VerifyTrademarkError",
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "/schemas/core/error.json"
+ },
+ "minItems": 1
+ },
+ "context": {
+ "$ref": "/schemas/core/context.json"
+ },
+ "ext": {
+ "$ref": "/schemas/core/ext.json"
+ }
+ },
+ "required": ["errors"],
+ "additionalProperties": true,
+ "not": {
+ "anyOf": [
+ { "required": ["status"] }
+ ]
+ }
+ }
+ ],
+ "properties": {}
+}
diff --git a/tests/check-platform-agnostic.cjs b/tests/check-platform-agnostic.cjs
index f25583e418..54f5796810 100644
--- a/tests/check-platform-agnostic.cjs
+++ b/tests/check-platform-agnostic.cjs
@@ -77,6 +77,14 @@ const ENUM_VALUE_ALLOWLIST = [
{ value: 'amazon', pathContains: 'brand.json' },
{ value: 'roku', pathContains: 'brand.json' },
+ // brand/verify-property-request.json — store enum mirrors brand.json's
+ // properties[].store. Factoring into a shared enums/property-store.json
+ // is tracked as cleanup; same canonical-identifier justification applies.
+ { value: 'apple', pathContains: 'brand/verify-property-request.json' },
+ { value: 'google', pathContains: 'brand/verify-property-request.json' },
+ { value: 'amazon', pathContains: 'brand/verify-property-request.json' },
+ { value: 'roku', pathContains: 'brand/verify-property-request.json' },
+
// brand.json — feed_format: google_merchant_center and facebook_catalog are
// widely-adopted open interchange formats implemented by many third parties.
{ value: 'google_merchant_center', pathContains: 'brand.json' },