Validates a spec-compliant ARD ai-catalog.json for agent discovery
The auditor resolves the site origin from the audited URL and locates ai-catalog.json:
Well-known URI β GET {origin}/.well-known/ai-catalog.json.
Agentmap fallback β if the well-known path is missing or returns non-JSON (for example an HTML catch-all), parse /robots.txt for an Agentmap: directive.
HTML fallback β otherwise look for <link rel="ai-catalog" href="..."> on the audited page.
Header checks β the catalog response should be JSON (Content-Type: application/json or a +json type) with Access-Control-Allow-Origin so crawlers can fetch it.
Schema checks β specVersion must be 1.0; entries must be an array; each entry needs identifier, displayName, type, and exactly one of url or data; identifiers must match urn:air:<publisher>:<namespace>:<agent-name>. Nested application/ai-catalog+json data objects are checked one level deep with the same entry rules.
Specification pin β every run emits 37A-AiCatalogAuditor-100 (INFORMATION) naming ARD v0.91 (26 Aug 2026) and ai-catalog.schema.json commit 4a8a6b8fdd3ac4a50dcb63213573159c1eed7856 (20 Jun 2026). That check does not change overall status.
Aggregate β any failing rule makes the audit FAIL; otherwise warnings (CORS, empty entries, omitted host, representativeQueries size, invalid updatedAt) make it WARNING; otherwise SUCCESS.
A missing catalog or a JSON parse error produces a FAIL and still includes the specification pin check.
Every row maps to one checkRule* method and one CheckCode. Nested inline catalogs reuse the same entry rules (402β414, 301, 303β304); hosting rules (400, 401, 300) and host object rules (404, 302) apply only to the HTTP catalog response. 37A-AiCatalogAuditor-100 is emitted on every run, including missing-catalog and invalid-JSON failures. CheckCode suffixes use status ranges: 100 information, 200 success, 300β399 warning, 400β499 fail, 500β599 error.
| CheckCode | Status | When it fires | Recommendation |
|---|---|---|---|
37A-AiCatalogAuditor-100 | INFORMATION | Every run. Records that this auditor implements ARD v0.91 and the pinned ai-catalog.schema.json commit. | This auditor implements ARD v0.91; catalog documents still use specVersion "1.0". Schema pin 4a8a6b8fdd3ac4a50dcb63213573159c1eed7856. |
37A-AiCatalogAuditor-400 | FAIL | The catalog HTTP response has a missing or blank Content-Type header. | Send Content-Type: application/json for {catalogUrl}. |
37A-AiCatalogAuditor-401 | FAIL | Content-Type is present but is not application/json or a +json type. | Serve {catalogUrl} as application/json instead of {contentType}. |
37A-AiCatalogAuditor-402 | FAIL | specVersion is missing or is not "1.0". | Set specVersion to "1.0" on {catalogUrl}; current value is {observed}. |
37A-AiCatalogAuditor-403 | FAIL | entries is missing or is not an array. | Add an entries array on {catalogUrl} listing the advertised agentic resources. |
37A-AiCatalogAuditor-404 | FAIL | host is present but host.displayName is missing or blank. | Set host.displayName on {catalogUrl} to a human-readable publisher name. |
37A-AiCatalogAuditor-405 | FAIL | A catalog entry has no identifier. | Set identifier on entry {label} in {catalogUrl} to a urn:air:<publisher>:<namespace>:<agent-name> URN. |
37A-AiCatalogAuditor-406 | FAIL | A catalog entry has no displayName. | Set displayName on entry {label} in {catalogUrl}. |
37A-AiCatalogAuditor-407 | FAIL | A catalog entry has no type. | Set type on entry {label} in {catalogUrl} to an IANA media type such as application/mcp-server-card+json. |
37A-AiCatalogAuditor-408 | FAIL | An entry identifier is present but does not match urn:air:<publisher>:<namespace>:<agent-name>. | Change {identifier} in {catalogUrl} to a domain-anchored URN such as urn:air:example.com:agent:assistant. |
37A-AiCatalogAuditor-409 | FAIL | An entry has both url and data. | Keep only url or only data on entry {label} in {catalogUrl}; the schema forbids both. |
37A-AiCatalogAuditor-410 | FAIL | An entry has neither url nor data. | Add url or inline data on entry {label} in {catalogUrl}. |
37A-AiCatalogAuditor-411 | FAIL | A trustManifest object is present without identity. | Set trustManifest.identity on {label} in {catalogUrl} to a SPIFFE ID, DID, or HTTPS FQDN URI. |
37A-AiCatalogAuditor-412 | FAIL | An attestation object has no type. | Set attestation.type on {label} in {catalogUrl} (e.g. SOC2-Type2). |
37A-AiCatalogAuditor-413 | FAIL | An attestation object has no uri. | Set attestation.uri on {label} in {catalogUrl} to the attestation document URL. |
37A-AiCatalogAuditor-414 | FAIL | An attestation object has no mediaType. | Set attestation.mediaType on {label} in {catalogUrl} (e.g. application/pdf). |
37A-AiCatalogAuditor-415 | FAIL | No catalog was found at /.well-known/ai-catalog.json, robots.txt Agentmap:, or <link rel="ai-catalog">. | Publish ai-catalog.json at /.well-known/ai-catalog.json for {url}, or advertise it with an Agentmap: robots.txt directive or <link rel="ai-catalog">. |
37A-AiCatalogAuditor-416 | FAIL | A catalog was found but the body is not valid JSON. | Fix the JSON served for {url}; parser reported {detail}. |
37A-AiCatalogAuditor-300 | WARNING | The catalog response has no Access-Control-Allow-Origin header. | Add Access-Control-Allow-Origin: * on {catalogUrl} so discovery crawlers can fetch the catalog. |
37A-AiCatalogAuditor-301 | WARNING | entries is an empty array. | Add at least one catalog entry on {catalogUrl} so agents can discover capabilities. |
37A-AiCatalogAuditor-302 | WARNING | The root host object is omitted. | Add a host object with displayName on {catalogUrl} so publishers can be identified. |
37A-AiCatalogAuditor-303 | WARNING | representativeQueries is present but does not contain 2β5 strings. | Change representativeQueries on {label} in {catalogUrl} from size {observed} to 2β5 examples. |
37A-AiCatalogAuditor-304 | WARNING | updatedAt is present but is not an ISO 8601 date-time. | Set updatedAt on {label} in {catalogUrl} to an ISO 8601 timestamp such as 2026-01-15T12:00:00Z instead of {observed}. |
37A-AiCatalogAuditor-500 | ERROR | The audited URL is null or blank. | Pass a non-blank page URL instead of {observed} so the auditor can discover ai-catalog.json. |
37A-AiCatalogAuditor-501 | ERROR | The audited URL is not a valid URI. | Pass an absolute HTTP(S) URL instead of {url}; parser reported {detail}. |
37A-AiCatalogAuditor-502 | ERROR | Discovery failed with an unexpected exception. | Retry the audit of {url}; the auditor failed with {detail}. |
37A-AiCatalogAuditor-200 | SUCCESS | Every rule above passed; one SUCCESS check is added with the message AI catalog at "{catalogUrl}" is valid: specVersion {specVersion}, {entryCount} entries. | β |
| Status | Description | Test logic |
|---|---|---|
| INFORMATION | Provenance only; does not change overall status | Every run includes 37A-AiCatalogAuditor-100 with ARD v0.91 and schema commit 4a8a6b8f⦠in the message and Check.data. |
| SUCCESS | A catalog was found and every publishing and schema rule passed | Well-known, Agentmap, or HTML rel="ai-catalog" returned parseable JSON; Content-Type is JSON; specVersion is 1.0; entries is a non-empty array; each entry has a valid urn:air identifier, displayName, type, and exactly one of url or data; CORS is present. The specification pin check is still present. |
| WARNING | The catalog is usable but incomplete for crawlers or search | Missing Access-Control-Allow-Origin (300); empty entries (301); omitted host (302); representativeQueries not sized 2β5 (303); or updatedAt not ISO 8601 (304). No FAIL rule fired. The specification pin check is still present. |
| FAIL | The catalog is missing, not JSON, or violates a schema MUST | Not found at well-known / Agentmap / HTML link (415); unparseable JSON (416); missing or non-JSON Content-Type (400, 401); specVersion not 1.0 (402); no entries array (403); host.displayName missing (404); missing entry fields (405β407); invalid URN (408); both or neither url/data (409, 410); trustManifest without identity (411); incomplete attestations (412β414). The specification pin check is still present. |
| ERROR | The request cannot be audited | The audited URL is null or blank (500), not a valid URI (501), or discovery threw (502). Checks include the specification pin (100) and the process error. |
Agents and federated registries look first at /.well-known/ai-catalog.json. An HTML SPA that serves 200 for every path hides the catalog unless Content-Type is JSON or an Agentmap / HTML link points at the real file.
CORS (Access-Control-Allow-Origin: *) is required for browser-based crawlers. A catalog that exists but cannot be read cross-origin is effectively unpublished.
Identifiers must stay urn:air: domain-anchored names. HTTP URLs in identifier break federation uniqueness and trust binding described in the ARD specification.
Strict value-or-reference (url XOR data) keeps parsers from choosing between two payloads. Both or neither makes the entry unusable.
representativeQueries (2β5 examples) are how registries build semantic embeddings. Omitting them is allowed; a list of the wrong size is a warning because search ranking will be weak or rejected by schema-strict consumers.
Add this auditor to your monitoring setup and start identifying issues on your websites today.