diff --git a/.changeset/registry-p0-followups-type-guard-docs-ia.md b/.changeset/registry-p0-followups-type-guard-docs-ia.md
new file mode 100644
index 0000000000..178671a136
--- /dev/null
+++ b/.changeset/registry-p0-followups-type-guard-docs-ia.md
@@ -0,0 +1,10 @@
+---
+---
+
+Registry P0 follow-ups from #3495: fix crawler type reclassification guard so agents with
+wrong non-unknown types can be corrected on future crawl passes; rename OpenAPI tag from
+"Lookups & Authorization" to "Authorization Lookups" to fix the %26-encoded URL slug; add
+trust-level descriptions to source/member/discovered_from schema fields; document auth-aware
+visibility on /operator; add missing operator/publisher rows to docs/registry/index.mdx
+overview table; add docs/registry/registering-an-agent.mdx explaining the two registration
+paths and trust levels.
diff --git a/docs.json b/docs.json
index 93d9efe6d5..3a81afaab2 100644
--- a/docs.json
+++ b/docs.json
@@ -505,7 +505,8 @@
"directory": "docs/registry/api-reference"
},
"pages": [
- "docs/registry/index"
+ "docs/registry/index",
+ "docs/registry/registering-an-agent"
]
},
"docs/reference/gmsf-reference",
@@ -997,7 +998,8 @@
"directory": "docs/registry/api-reference"
},
"pages": [
- "docs/registry/index"
+ "docs/registry/index",
+ "docs/registry/registering-an-agent"
]
},
"docs/reference/gmsf-reference",
diff --git a/docs/registry/index.mdx b/docs/registry/index.mdx
index 03de740bed..b14ccfd1c5 100644
--- a/docs/registry/index.mdx
+++ b/docs/registry/index.mdx
@@ -84,9 +84,12 @@ Rate-limited endpoints return `429 Too Many Requests` when the limit is exceeded
Poll a cursor-based feed of registry changes for local sync.
-
+
Look up agents by domain, validate product authorization, and check property authorization in real time.
+
+ How agents appear in the registry, what trust level each path produces, and what membership unlocks.
+
### Brand Resolution
@@ -139,10 +142,12 @@ All sources produce the same resolution response structure. To get full brand id
| GET | `/api/registry/feed` | Poll cursor-based registry change feed (auth required) |
-### Lookups & Authorization
+### Authorization Lookups
| Method | Path | Description |
|--------|------|-------------|
+| GET | `/api/registry/operator` | What agents does this domain operate? (auth-aware — see [operator lookup](#operator-and-publisher-lookup)) |
+| GET | `/api/registry/publisher` | What does this domain publish and which agents does it authorize? |
| GET | `/api/registry/lookup/domain/{domain}` | Find agents authorized for a domain |
| GET | `/api/registry/lookup/property` | Find agents by property identifier |
| GET | `/api/registry/lookup/agent/{agentUrl}/domains` | Get all domains for an agent |
@@ -150,6 +155,17 @@ All sources produce the same resolution response structure. To get full brand id
| POST | `/api/registry/expand/product-identifiers` | Expand property selectors to identifiers |
| GET | `/api/registry/validate/property-authorization` | Real-time authorization check |
+#### Operator and publisher lookup
+
+`/api/registry/operator` and `/api/registry/publisher` answer different questions about the same domain:
+
+| Endpoint | Question | Typical caller |
+|----------|----------|----------------|
+| `/api/registry/operator?domain=X` | What agents does this entity operate? Which publishers trust it? | Buy-side: validating a sell-side counterparty |
+| `/api/registry/publisher?domain=X` | What inventory does this entity publish? Which agents does it authorize? | Sell-side: inspecting a publisher's authorization set |
+
+The operator endpoint returns more data for authenticated callers: anonymous callers see public agents only; callers with an AgenticAdvertising.org API-access token additionally see `members_only` agents; callers who are the profile owner additionally see `private` agents. [See registering an agent](/docs/registry/registering-an-agent) for how trust levels map to registration source.
+
Authorization validation checks both sides: the publisher's `adagents.json` (does it authorize this agent with the claimed `delegation_type`?) and the operator's `brand.json` (does it declare this property with a matching `relationship`?).
### Validation Tools
diff --git a/docs/registry/registering-an-agent.mdx b/docs/registry/registering-an-agent.mdx
new file mode 100644
index 0000000000..64ed989666
--- /dev/null
+++ b/docs/registry/registering-an-agent.mdx
@@ -0,0 +1,57 @@
+---
+title: Registering an Agent
+sidebarTitle: Registering an Agent
+description: "How agents appear in the AgenticAdvertising.org registry, what trust level each path produces, and what AgenticAdvertising.org membership unlocks."
+"og:title": "AdCP — Registering an Agent"
+---
+
+Agents appear in the AgenticAdvertising.org registry through two paths. The path determines the agent's `source` value in API responses and the trust level assigned to it.
+
+## Registration paths
+
+| Path | How | `source` in API | Trust level |
+|------|-----|-----------------|-------------|
+| **Member direct registration** | An AgenticAdvertising.org member organization enrolls the agent via the member dashboard. The member has accepted AgenticAdvertising.org terms on the agent's behalf. | `registered` | Full — appears in `public`, `members_only`, and `private` tiers depending on visibility settings |
+| **Discovery via adagents.json** | A member publisher lists the agent in their `adagents.json`. The registry crawler finds and indexes it automatically. The agent itself has not opted in. | `discovered` | Public only — always visible as a `public` agent, regardless of caller authentication |
+
+## What trust level looks like in API responses
+
+The `source` field is returned on every agent in `GET /api/registry/agents`.
+
+```json Registered agent (member direct)
+{
+ "url": "https://ads.streamhaus.example.com",
+ "type": "sales",
+ "source": "registered",
+ "member": { "slug": "streamhaus", "display_name": "StreamHaus" }
+}
+```
+
+```json Discovered agent (via publisher adagents.json)
+{
+ "url": "https://agent.examplepub.com",
+ "type": "sales",
+ "source": "discovered",
+ "discovered_from": { "publisher_domain": "examplepub.com" }
+}
+```
+
+A `discovered` agent's `member` field is absent. It was listed by a publisher, not enrolled by the agent operator. If you are the operator of a `discovered` agent and want to upgrade to `registered` status, the path is AgenticAdvertising.org membership (see below).
+
+## What AgenticAdvertising.org membership unlocks
+
+Member organizations that enroll agents gain:
+
+- **`registered` source** — callers can distinguish your agent from crawl-discovered entries
+- **Visibility tiers** — choose `public` (default), `members_only` (visible only to authenticated AgenticAdvertising.org API callers), or `private` (visible only when you are the caller)
+- **`member` field populated** — your organization name and slug appear alongside the agent in registry responses
+- **Verified badge** — displayed in the public registry UI
+- **Storyboard testing access** — run your agent against the AdCP compliance suite
+
+To enroll as an AgenticAdvertising.org member, visit [agenticadvertising.org/join](https://agenticadvertising.org/join).
+
+## Getting a non-member agent discovered
+
+If you are not yet an AgenticAdvertising.org member, the only path into the registry is through a member publisher's `adagents.json`. Ask the publisher to add your agent URL with the appropriate `authorized_for` value. The registry crawler runs on a regular schedule and will pick up the entry on its next pass.
+
+Self-registration without AgenticAdvertising.org membership is on the roadmap. A separate tracking issue will be filed once the trust-tier model for self-attested agents is decided.
diff --git a/scripts/generate-openapi.ts b/scripts/generate-openapi.ts
index ba6f681c7a..dbcdbf5733 100644
--- a/scripts/generate-openapi.ts
+++ b/scripts/generate-openapi.ts
@@ -89,7 +89,7 @@ const TAG_DESCRIPTIONS: Record = {
"Property Resolution": "Resolve publisher domains to their property configurations and authorized agents.",
"Agent Discovery": "Browse the federated agent network, search agent inventory profiles, publisher index, and registry statistics.",
"Change Feed": "Poll cursor-based registry change events for local sync.",
- "Lookups & Authorization": "Look up agents by domain or property, and validate ad-serving authorization.",
+ "Authorization Lookups": "Look up agents by domain or property, and validate ad-serving authorization.",
"Validation Tools": "Validate publisher adagents.json files and generate compliant configurations.",
"Search": "Cross-entity search across brands, publishers, agents, and properties.",
"Agent Probing": "Connect to live agents and inspect their capabilities, formats, and inventory.",
diff --git a/server/src/crawler.ts b/server/src/crawler.ts
index 0f0c96b380..8c352d80e6 100644
--- a/server/src/crawler.ts
+++ b/server/src/crawler.ts
@@ -577,7 +577,7 @@ export class CrawlerService {
this.snapshotDb.upsertHealth(agent.url, health, stats),
]);
- if (!knownTypes.has(agent.url) && inferredType !== 'unknown') {
+ if (inferredType !== 'unknown' && inferredType !== knownTypes.get(agent.url)) {
await this.federatedIndex.updateAgentMetadata(agent.url, {
agent_type: inferredType,
protocol: profile.protocol,
diff --git a/server/src/routes/registry-api.ts b/server/src/routes/registry-api.ts
index 6f7f8d8103..2ba11d4358 100644
--- a/server/src/routes/registry-api.ts
+++ b/server/src/routes/registry-api.ts
@@ -608,14 +608,14 @@ registry.registerPath({
},
});
-// Lookups & Authorization
+// Authorization Lookups
registry.registerPath({
method: "get",
path: "/api/registry/lookup/domain/{domain}",
operationId: "lookupDomain",
summary: "Domain lookup",
description: "Find all agents authorized for a given publisher domain.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: { params: z.object({ domain: z.string().openapi({ example: "examplepub.com" }) }) },
responses: {
200: { description: "Domain lookup result", content: { "application/json": { schema: DomainLookupResultSchema } } },
@@ -628,7 +628,7 @@ registry.registerPath({
operationId: "lookupProperty",
summary: "Property identifier lookup",
description: "Find agents that hold a specific property identifier.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: { query: z.object({ type: z.string(), value: z.string() }) },
responses: {
200: { description: "Matching agents", content: { "application/json": { schema: z.object({ type: z.string(), value: z.string(), agents: z.array(z.unknown()), count: z.number().int() }) } } },
@@ -641,7 +641,7 @@ registry.registerPath({
operationId: "getAgentDomains",
summary: "Agent domain lookup",
description: "Get all publisher domains associated with an agent.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: { params: z.object({ agentUrl: z.string() }) },
responses: {
200: { description: "Domains for the agent", content: { "application/json": { schema: z.object({ agent_url: z.string(), domains: z.array(z.string()), count: z.number().int() }) } } },
@@ -653,8 +653,12 @@ registry.registerPath({
path: "/api/registry/operator",
operationId: "lookupOperator",
summary: "Operator lookup",
- description: "Given a domain, returns the agents this entity operates and which publishers trust them.",
- tags: ["Lookups & Authorization"],
+ description:
+ "Given a domain, returns the agents this entity operates and which publishers trust them. " +
+ "Response visibility is caller-dependent: anonymous callers see public agents only; " +
+ "callers with an AgenticAdvertising.org API-access token additionally see members_only agents; " +
+ "callers who are the profile owner additionally see private agents.",
+ tags: ["Authorization Lookups"],
request: {
query: z.object({
domain: z.string().openapi({ example: "pubmatic.com" }),
@@ -672,7 +676,7 @@ registry.registerPath({
operationId: "lookupPublisher",
summary: "Publisher lookup",
description: "Given a domain, returns the inventory this entity publishes and which agents it authorizes.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: {
query: z.object({
domain: z.string().openapi({ example: "voxmedia.com" }),
@@ -691,7 +695,7 @@ registry.registerPath({
summary: "Validate product authorization",
description:
"Check whether an agent is authorized to sell a product based on its publisher_properties.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: {
body: {
content: {
@@ -715,7 +719,7 @@ registry.registerPath({
operationId: "expandProductIdentifiers",
summary: "Expand product identifiers",
description: "Expand publisher_properties selectors into concrete property identifiers for caching.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: {
body: {
content: {
@@ -753,7 +757,7 @@ registry.registerPath({
operationId: "validatePropertyAuthorization",
summary: "Property authorization check",
description: "Quick check if a property identifier is authorized for an agent. Optimized for real-time ad request validation.",
- tags: ["Lookups & Authorization"],
+ tags: ["Authorization Lookups"],
request: {
query: z.object({
agent_url: z.string(),
@@ -5160,7 +5164,7 @@ export function createRegistryApiRouter(config: RegistryApiConfig): Router {
}
});
- // ── Lookups & Authorization ───────────────────────────────────
+ // ── Authorization Lookups ─────────────────────────────────────
router.get("/registry/operator", optAuth, async (req, res) => {
const rawDomain = req.query.domain as string;
diff --git a/server/src/schemas/registry.ts b/server/src/schemas/registry.ts
index f254256152..b700f6ad0d 100644
--- a/server/src/schemas/registry.ts
+++ b/server/src/schemas/registry.ts
@@ -353,9 +353,23 @@ export const FederatedAgentWithDetailsSchema = z
})
.optional(),
added_date: z.string().optional(),
- source: z.enum(["registered", "discovered"]).optional(),
- member: MemberRefSchema.optional(),
- discovered_from: DiscoveredFromSchema.optional(),
+ source: z
+ .enum(["registered", "discovered"])
+ .optional()
+ .openapi({
+ description:
+ "How this agent entered the registry. 'registered' means the operator directly enrolled " +
+ "via AgenticAdvertising.org and has accepted its terms. 'discovered' means the agent was " +
+ "found in a member publisher's adagents.json and has not explicitly opted in.",
+ }),
+ member: MemberRefSchema.optional().openapi({
+ description:
+ "AgenticAdvertising.org member organization that registered this agent. Present only when source='registered'.",
+ }),
+ discovered_from: DiscoveredFromSchema.optional().openapi({
+ description:
+ "The member publisher whose adagents.json referenced this agent. Present only when source='discovered'.",
+ }),
health: AgentHealthSchema.optional(),
stats: AgentStatsSchema.optional(),
capabilities: AgentCapabilitiesSchema.optional(),
diff --git a/static/openapi/registry.yaml b/static/openapi/registry.yaml
index 5f1b1bddc5..ef7cadb152 100644
--- a/static/openapi/registry.yaml
+++ b/static/openapi/registry.yaml
@@ -2579,7 +2579,7 @@ paths:
summary: Domain lookup
description: Find all agents authorized for a given publisher domain.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -2600,7 +2600,7 @@ paths:
summary: Property identifier lookup
description: Find agents that hold a specific property identifier.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -2640,7 +2640,7 @@ paths:
summary: Agent domain lookup
description: Get all publisher domains associated with an agent.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -2673,7 +2673,7 @@ paths:
summary: Operator lookup
description: Given a domain, returns the agents this entity operates and which publishers trust them.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -2700,7 +2700,7 @@ paths:
summary: Publisher lookup
description: Given a domain, returns the inventory this entity publishes and which agents it authorizes.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -2727,7 +2727,7 @@ paths:
summary: Validate product authorization
description: Check whether an agent is authorized to sell a product based on its publisher_properties.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
requestBody:
content:
application/json:
@@ -2768,7 +2768,7 @@ paths:
summary: Expand product identifiers
description: Expand publisher_properties selectors into concrete property identifiers for caching.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
requestBody:
content:
application/json:
@@ -2834,7 +2834,7 @@ paths:
summary: Property authorization check
description: Quick check if a property identifier is authorized for an agent. Optimized for real-time ad request validation.
tags:
- - Lookups & Authorization
+ - Authorization Lookups
parameters:
- schema:
type: string
@@ -6000,7 +6000,7 @@ tags:
description: Browse the federated agent network, search agent inventory profiles, publisher index, and registry statistics.
- name: Change Feed
description: Poll cursor-based registry change events for local sync.
- - name: Lookups & Authorization
+ - name: Authorization Lookups
description: Look up agents by domain or property, and validate ad-serving authorization.
- name: Validation Tools
description: Validate publisher adagents.json files and generate compliant configurations.