ardregistry.net

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.

nginx
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

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.

vercel.json
{
  "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
# _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

Next.js app router
// 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

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

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

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

functions.php or a small plugin
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.

HTML link tag
<!-- 1. link relation, in the head of any page -->
<link rel="ard" href="https://cdn.example.com/entries.json">
robots.txt
# 2. robots.txt, one line
Agentmap: https://example.com/entries.json
DNS SVCB
; 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.

one call, any ARD registry that accepts submissions
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

the two commands that catch most problems
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).