Guide
Publishing an ARD manifest
One requirement, a dozen ways to satisfy it, and one failure mode that accounts for most of the manifests that quietly never get indexed.
A GET to https://yourdomain.com/.well-known/ard.json must return 200, valid JSON, and Content-Type: application/json to an unauthenticated client. The usual failure is an application framework answering an unknown path with its HTML shell and a 200 status, which a crawler cannot distinguish from a broken manifest.
The requirement, in one line
A GET to https://yourdomain.com/.well-known/ard.json must return HTTP 200, a body
that parses as JSON, and Content-Type: application/json, to a client with no cookie,
no token and no referrer.
That is the whole contract. Everything below is a way of satisfying that one line. If your stack is not here, the test is the same: curl it from a machine that has never seen your site.
Static hosting
nginx
Files under a directory beginning with a dot are served normally by nginx, but many hardening templates include a rule that blocks them. Check for one before assuming this works.
location /.well-known/ {
alias /var/www/example.com/.well-known/;
default_type application/json;
add_header Cache-Control "public, max-age=3600";
}
# If a rule like this exists anywhere in your config, it will block the manifest:
# location ~ /\. { deny all; }
# Make the .well-known location more specific than it, or exclude .well-known from it.
Apache
<Directory "/var/www/example.com/.well-known">
Require all granted
</Directory>
AddType application/json .json
Netlify, Vercel, Cloudflare Pages
Put the file in your published output directory. All three serve .well-known from a
static build without extra configuration; the failure mode is a build step that strips dotfiles.
For Vercel, set the header explicitly because it infers text/plain for some paths.
{
"headers": [
{ "source": "/.well-known/ard.json",
"headers": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Cache-Control", "value": "public, max-age=3600" }
] }
]
}
GitHub Pages
Jekyll ignores files and directories starting with a dot by default, which silently drops your manifest from the build. Add it back:
# _config.yml
include:
- .well-known
Application frameworks
Every framework in this section has the same failure: an unknown path falls through to a catch-all route and returns your application shell with a 200 status. To a crawler that looks like a manifest that is not JSON, and it moves on.
Next.js
// Static: put it at public/.well-known/ard.json and it is served as-is.
// Dynamic, so entries can be generated: app/.well-known/ard.json/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json(manifest, {
headers: { "Cache-Control": "public, max-age=3600" },
});
}
FastAPI
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/.well-known/ard.json", include_in_schema=False)
async def ard_manifest():
return JSONResponse(MANIFEST, headers={"Cache-Control": "public, max-age=3600"})
Express
app.get("/.well-known/ard.json", (req, res) => {
res.type("application/json")
.set("Cache-Control", "public, max-age=3600")
.send(manifest);
});
// Register this BEFORE any catch-all route or SPA fallback.
Django
# urls.py
from django.http import JsonResponse
from django.urls import path
def ard_manifest(request):
return JsonResponse(MANIFEST)
urlpatterns = [ path(".well-known/ard.json", ard_manifest) ] + urlpatterns
WordPress
add_action("init", function () {
if ($_SERVER["REQUEST_URI"] !== "/.well-known/ard.json") return;
header("Content-Type: application/json");
header("Cache-Control: public, max-age=3600");
echo file_get_contents(__DIR__ . "/ard.json");
exit;
});
The three alternatives to the well-known path
When you cannot put a file at the root, the specification gives you options that consumers are required to honour.
<!-- 1. link relation, in the head of any page -->
<link rel="ard" href="https://cdn.example.com/entries.json">
# 2. robots.txt, one line
Agentmap: https://example.com/entries.json
; 3. DNS, for a static entry source or a registry endpoint
_entries._agents.example.com. IN SVCB 1 entries.example.com.
_search._agents.example.com. IN SVCB 1 registry.example.com.
Caching and updates
An hour of cache is a reasonable default. Registries re-crawl on their own schedules and none of
them promise one, so do not set a long TTL and expect a fast update. Carry updatedAt
on your entries: freshness is an input to ranking, and it is how a registry decides you are worth
re-visiting.
Step three: tell a registry it exists
This is the step that gets skipped, and skipping it is why plenty of correct manifests are never indexed. Publishing is decentralised, which means nobody has to approve you and also that nobody is watching. A registry can only crawl a domain it has encountered somehow, and if nothing links to you, nothing has encountered you.
The scale of it: of the 6,610 publishers in the public index, eleven have ever submitted a domain or an endpoint. Everything else was found by crawling something that already pointed at it. Measured 4 September 2026 from that registry's submission log.
Get indexed
Publishing the file is half of it. A registry can only index a domain it has encountered, so tell the ones you care about.
curl -X POST https://neuronto.com/submit \
-H "Content-Type: application/json" \
-d '{"url": "https://yourdomain.com"}'
Disclosure: Neuronto is run by the people who write this
site, and it is named first for that reason as much as any other. It resolves a bare domain to
your manifest, indexes entries of every type rather than MCP servers only, and answers with what
it found or exactly what it tried. Add "dry_run": true to see the outcome without
being written anywhere.
The other public registries take submissions through their own doors, and submitting to several costs nothing: WellKnown, ARD Registry Hub, Desvela. What actually reaches all of them over time is the manifest on your own domain, which is the part nobody can take away from you. How each one behaves, probed.
Verify it from outside
curl -s -D - -o /dev/null https://yourdomain.com/.well-known/ard.json
# HTTP/2 200
# content-type: application/json
curl -s https://yourdomain.com/.well-known/ard.json | python3 -m json.tool | head
# if this prints an error, you are serving HTML with a 200
Last reviewed 2026-09-04. Checked against ARD v0.91 (Proposal, 2026-08-26).