api-catalog and OpenAPI: how an agent finds your API
No OpenAPI spec at a conventional path, or OAuth metadata with no way for an agent to register a client. squirrel probes both.
What API discovery is
An agent that wants to call an API needs three things it can fetch rather than read: a description of the endpoints, the location of the authorization server, and a way to obtain client credentials.
- An OpenAPI document describes the endpoints.
/.well-known/api-catalog, defined by RFC 9727, is a link set pointing at one or more of them. - RFC 8414 defines
/.well-known/oauth-authorization-server, the authorization server metadata. RFC 9728 defines/.well-known/oauth-protected-resource, which points a client from a resource back at the server that guards it. - RFC 8414’s metadata can carry a
registration_endpoint, which points at the Dynamic Client Registration endpoint defined by RFC 7591, so a client can register itself. Client ID Metadata Documents are the newer alternative, signalled byclient_id_metadata_document_supported.
Without the last one, an agent can find the authorization server and then stop, because a human still has to create a client by hand.
What squirrel checks
The rule runs once per crawl against probed well-known paths. A probe counts as a hit when it returned 200, the body parsed as JSON, and it did not sniff as HTML. It emits one or two checks:
api-discovery: reports at info status with the messageAPI and OAuth discovery documents not checked: the discovery probe was not sentand the valuenot-checkedwhen no well-known probe ran, and no second check follows. With no hit and at least one of the four API paths never requested or unanswered, it reports at info status withAPI discovery documents not checked: <reason>, where the reason isthe discovery probe was not sentorthe discovery probe got no answer, listing those paths indetails.notChecked. Otherwise it reports at info status withNo api-catalog or OpenAPI specification foundand the valueabsent, or withAPI discovery document(s) found: <list>, where the list holdsapi-catalog (<path>)andOpenAPI spec (<path>)for whichever was found, joined with,. The OpenAPI paths probed are/openapi.json,/swagger.jsonand/api/openapi.json, and a hit at one of them counts only when the document names its own spec version, meaning a top-levelopenapiorswaggerkey or that key visible in the excerpt.api-discovery-oauth: reports at info status withOAuth discovery documents not checked: <reason>(same two reasons) and the valuenot-checkedwhen neither OAuth path is a hit and at least one was never requested or unanswered, and withNo OAuth discovery documents foundwhen neither is a hit and both answered. Otherwise the message opensOAuth discovery found: <found>, where<found>listsauthorization server metadata (RFC 8414)andprotected-resource metadata (RFC 9728)for whichever was present, joined with,. With the protected-resource document alone the message stops there. With the authorization server metadata and a self-onboarding mechanism it continues; agent self-onboarding available via <mechanisms>, namingDynamic Client Registration (RFC 7591),Client ID Metadata Documents (CIMD)or both joined withand. With the metadata and neither mechanism it continues, but no registration_endpoint (DCR) or client_id_metadata_document_supported (CIMD) — an agent can find the authorization server but a human still has to register a client by hand.
Every result here is info. Not every site has an API, so absence is never a defect and the rule cannot move the score.
A probe goes unsent when discovery probes are turned off, when no rule that reads it was enabled for the crawl, or when the crawl’s preamble time budget ran out first. A probe that went out and got no answer, a refused connection or a timeout, counts the same. None of those says anything about the site, so the result is not-checked, never absent.
How to fix it
Publish the spec at a conventional path and, if you already have a .well-known directory, point at it:
{
"linkset": [{
"anchor": "https://example.com",
"service-desc": [{ "href": "https://example.com/openapi.json", "type": "application/json" }]
}]
}That document goes at /.well-known/api-catalog. If the API uses OAuth, add registration_endpoint to your authorization server metadata so an agent can register a client without a support ticket.
The common false positive is an API documented under a path this rule does not probe, such as /v2/openapi.yaml. The check reports absence because it looks at three fixed paths. Adding an api-catalog that points at the real location resolves it and is the point of the RFC.
| Rule ID | ax/api-discovery |
| Category | Agent Experience |
| Scope | Site-wide |
| Severity | info |
| Weight | 1/10 |
Enable / disable
Disable this rule
[rules]
disable = ["ax/api-discovery"]Disable all Agent Experience rules
[rules]
disable = ["ax/*"]Enable only this rule
[rules]
enable = ["ax/api-discovery"]enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.
Related rules
- ax/well-known-agent: MCP and A2A manifests, the other machine-readable descriptors under
.well-known. - ax/agents-md: the human-readable instructions for a coding agent working on the codebase.
- ax/llms-txt: the curated index that can link to the API docs.
- security/https: the transport requirement every OAuth flow depends on.
Agent experience findings ship in every audit next to the SEO, performance and security rules. See llms.txt for how an agent works through a report.
References
- The api-catalog Well-Known URI, RFC 9727
- OAuth 2.0 Authorization Server Metadata, RFC 8414
- OAuth 2.0 Protected Resource Metadata, RFC 9728
Check your site
Run squirrel audit https://example.com and open the Agent Experience section of the report. Each document found is listed with its path. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.