An agents.json is a JSON file at the root of a site that tells a program what the site is, who operates it, how to search it, how the operator would like to be crawled, and which machine interfaces exist. Ours is at /agents.json. This post goes through it field by field, explains the one rule we applied to it, which is that it advertises nothing that does not exist, describes exactly how check C4 tests for it, and is candid about the parts that are our invention rather than anyone's standard. Why most sites score zero on machine endpoints made the case for publishing one; this is the file itself.
How C4 tests for it
C4, Machine endpoints and agent manifests, is worth five points and rated High effort. It looks for four surfaces and scores by how many it finds: two or more earns five, one earns three, none earns zero. The evidence lists them under found.
The agents.json test is the only one that fetches a separate file. The crawler requests /agents.json, and the check counts it if the response was a 200 and the body parses as JSON. That is the whole test. The contents are not inspected, because there is no specification to inspect them against, a point this post returns to. A file that returns 200 but is not valid JSON does not count.
The other three are pattern matches over the homepage HTML. An OpenAPI spec is found if the page mentions openapi.json, openapi.yaml or a swagger equivalent anywhere in its markup. An MCP manifest is found if the page contains /.well-known/mcp, mcp.json or the string modelcontextprotocol. An API documentation link is found if there is an href to a path containing /docs or /api-docs, or a reference to a developer. or developers. subdomain.
Two consequences follow. First, the manifest on its own is three points, and a footer link to a documentation page takes it to five, which is the cheapest route to full marks on the rubric. Second, the three patterns only see the homepage. Our own footer links /docs and /api/v1/openapi.json on every page, which is what the documentation and OpenAPI patterns match; the MCP pattern needs its string on the homepage itself, so a server mentioned only on a docs page is not found by it.
The file, annotated
This is our manifest with the values abbreviated and the structure intact. The field names are the real ones.
{
"schemaVersion": "0.1",
"name": "AgentFriendlyRank",
"legalName": "Socio360",
"description": "…",
"url": "https://agentfriendlyrank.com",
"contact": { "email": "hello@…", "security": "security@…", "privacy": "dpo@…" },
"documentation": {
"llmsTxt": "https://agentfriendlyrank.com/llms.txt",
"llmsFullTxt": "https://agentfriendlyrank.com/llms-full.txt",
"methodology": "https://agentfriendlyrank.com/methodology",
"rubricVersion": "1.0.1"
},
"search": { "urlTemplate": "https://agentfriendlyrank.com/search?q={query}", "method": "GET" },
"crawling": {
"userAgent": "AgentFriendlyRankBot/1.0 (+https://agentfriendlyrank.com/bot)",
"policy": "https://agentfriendlyrank.com/bot",
"respectsRobotsTxt": true,
"maxPagesPerAudit": 6,
"maxConcurrentPerHost": 1,
"requestTimeoutSeconds": 10,
"optOut": "Add 'User-agent: AgentFriendlyRankBot' and 'Disallow: /' to robots.txt"
},
"endpoints": [
{ "type": "openapi", "name": "Read-only API", "url": "…/api/v1/openapi.json", "baseUrl": "…/api/v1", "authentication": "none", "documentation": "…/docs" },
{ "type": "mcp", "name": "AgentFriendlyRank MCP server", "url": "…/mcp", "transport": "streamable-http", "authentication": "none", "tools": ["get_site_report", "get_leaderboard", "compare_sites", "get_scan_status", "get_rubric"], "documentation": "…/docs#mcp" },
{ "type": "form", "name": "Request a scan", "url": "…/scan", "method": "POST", "note": "…" }
],
"policies": { "privacy": "…/privacy", "terms": "…/terms", "dataProcessing": "…/dpa" }
}
Identity
schemaVersion, name, legalName, description and url. The name is the same string that appears in our title element, og:site_name and Organization schema, because check D5 compares those and the manifest should not be the one place that disagrees. legalName is the operating entity, which is not the brand; an agent deciding whether to trust a site benefits from knowing both.
contact
Three addresses, because three different people answer them. email is general enquiries. security is the same address our security.txt carries, so a researcher who finds the manifest first and the well-known file second gets one answer. privacy is the data protection contact.
documentation
Links to llms.txt, llms-full.txt and the methodology page, plus rubricVersion. The version is not typed into the file; it is imported from the rubric package at build time, so it cannot lag behind a rubric change. Anything that can drift between two copies eventually does.
search
A URL template and the method. It says the same thing as the SearchAction in our WebSite schema, which check C6 reads: an agent can build a results URL by substituting the query, without touching the form.
crawling
This section is unusual for a manifest, and it exists because we crawl other people's sites. It states our bot's user agent, links its policy page at /bot, and records the limits the bot runs under: it respects robots.txt, fetches at most six pages per audit, one request at a time per host, with a ten-second timeout, and the optOut field gives the two lines of robots.txt that stop it. If you run a crawler, this is a fair place to say so.
endpoints
Three entries, each with a type, a name and a url. The openapi entry gives the spec URL, the baseUrl the paths are relative to, authentication: "none" and a link to the docs. The mcp entry gives the server URL, its transport, the same open authentication, the five tool names, and its docs. The form entry describes the scan form, its method, and a note explaining that it works without JavaScript and where it redirects. The type values are our vocabulary; nothing outside this site defines them.
policies
Privacy, terms and the data-processing agreement. Check D3 wants a privacy policy and a terms page linked from the site; this repeats those links in a place an agent reads first.
The file is generated statically and served with Content-Type: application/json; charset=utf-8 and Cache-Control: public, max-age=3600.
Why endpoints was empty until they existed
Until the API shipped, endpoints was []. The read-only API, its OpenAPI document and the MCP server were planned, and it would have been easy to list them ahead of time. We did not, and the reason applies to every field in every manifest.
An agent reads a manifest as an instruction. A person who finds a broken link shrugs and tries something else; a program that finds an endpoint in a manifest, requests it, and receives a 404 has learned that the manifest is unreliable, and a sensible program then discounts everything else in it. A manifest that advertises one thing that does not exist is worse than no manifest, because it costs the site the trust the file was published to earn. The rule we applied is simple: anything in agents.json must be fetchable today, and each entry was added when its endpoint shipped. The API is documented at /docs, the MCP server answers at /mcp, and the spec is at the URL the manifest gives, so the list is now three long.
What is unsettled
agents.json has no governing specification. Our schemaVersion is "0.1" because there is no authority to take a version number from, and the number is ours. The field names are ours as well: crawling, endpoints[].type, optOut, all of them. They were chosen to be readable and to match the pages they point at, but no other site is obliged to use them and no client is obliged to understand them. That is also why the scanner tests only that the file parses. Validating fields against a spec that does not exist would be inventing the spec and scoring people against it.
Some of what the file points at is settled. OpenAPI is a stable specification, published at spec.openapis.org, and publishing one is not a bet. MCP has a published specification at modelcontextprotocol.io and clients that consume it today, though the convention for discovering a server from a bare domain is still forming, which is why our manifest and our llms.txt both name the server explicitly. The manifest itself is the least settled of the three. If a specification for it emerges, we will adopt it, bump the rubric version, and record the change in the changelog; scores issued under the current version will not be restated.
Writing yours
Start with the parts that require no engineering: name, legalName, url, description, contact, documentation and policies, each pointing at pages you already have. Add search only if your search is a GET with a query parameter. Set endpoints to [] unless you have something to put in it, and resist listing a planned API. Serve the file at /agents.json with a 200 and an application/json content type, then confirm it parses:
curl -s https://example.com/agents.json | jq .
If jq prints the file back, the scanner will count it. Add a link to a documentation page in your footer and C4 is at full marks. The rubric rates C4 as High effort because the real machine surfaces, a documented API or an MCP server, are real engineering; the manifest is the one part of it that is an afternoon.
Check your own site
Run the free scan and read the C4 evidence; found lists which of the four surfaces the crawler could see, and an empty list is where most sites start. The full scoring is on the methodology page under actionability.