Skip to main content
This guide walks you through registering your service on the canonical AFAuth service directory at registry.afauth.org. The whole flow takes ~5 minutes once you can publish a file at your discovery host.
Listing is opt-in. The protocol does not require it. See The service directory for what a listing does and doesn’t mean.

Before you start

You need:
  1. A live AFAuth discovery document at https://your-host/.well-known/afauth. If you don’t have one yet, see the /.well-known/afauth reference.
  2. The ability to publish a file at https://your-host/.well-known/afauth-registry-proof (a single short text file).
  3. A service_did declared in your discovery document. did:web:your-host is the recommended form (and is the only form that carries a DNS anchor; did:key is also accepted but is rendered with a “no domain anchor” indicator on browse interfaces).

The four steps

1

Request a challenge

Ask the directory for a one-time challenge token bound to your discovery host:
curl -s -X POST https://registry.afauth.org/v1/listings/challenge \
  -H 'Content-Type: application/json' \
  -d '{ "discovery_url": "https://api.example.com/.well-known/afauth" }'
Response:
{
  "challenge_token": "ch_AbCd1ef-gHIj_klmN0pqRs",
  "proof_url":       "https://api.example.com/.well-known/afauth-registry-proof",
  "expires_at":      "2026-05-25T15:30:00Z"
}
The token format is ch_ followed by 22 url-safe-base64 characters (128 bits of entropy). Treat it as opaque — don’t try to parse it.
The challenge is single-use and expires in 30 minutes.
2

Publish the challenge token at the proof URL

Make proof_url respond with HTTP 200, Content-Type: text/plain, and the bare challenge token as the body. No surrounding JSON, no quotes, no trailing newline.
ch_AbCd1ef-gHIj_klmN0pqRs
Verify locally before submitting:
curl -s https://api.example.com/.well-known/afauth-registry-proof
# ch_AbCd1ef-gHIj_klmN0pqRs
The body must equal the challenge_token byte-for-byte.
For static hosts (S3, Cloudflare Pages, GitHub Pages, Vercel) just upload a plain text file at the right path. For dynamic services, add a route that returns the token from an env var.
3

Submit the listing

Tell the directory you’ve published the proof. It will fetch proof_url, fetch your discovery document, validate both, and create the listing:
curl -s -X POST https://registry.afauth.org/v1/listings \
  -H 'Content-Type: application/json' \
  -d '{
    "discovery_url":   "https://api.example.com/.well-known/afauth",
    "challenge_token": "ch_AbCd1ef-gHIj_klmN0pqRs",
    "title":           "Example Photo Storage",
    "description":     "AFAuth-supported photo storage for agents.",
    "tags":            ["productivity", "storage"]
  }'
title, description, and tags are optional and shown on the browse page.On success you get a session token scoped to your service_did:
{
  "service_did":   "did:web:api.example.com",
  "session_token": "sess_T1uvWxYz3456_AbCdEfGhIj",
  "expires_at":    "2026-06-01T15:30:00Z"
}
Session tokens are bearer credentials and expire in 7 days. Treat them like API keys — secret, short-lived, scoped to a single listing.You can now remove the proof file from /.well-known/afauth-registry-proof if you want; the directory does not re-fetch it except when you re-challenge.
4

Manage the listing with the session token

Update writeable fields with PATCH:
curl -s -X PATCH https://registry.afauth.org/v1/listings/did:web:api.example.com \
  -H 'Authorization: Bearer sess_T1uvWxYz3456_AbCdEfGhIj' \
  -H 'Content-Type: application/json' \
  -d '{ "tags": ["productivity", "storage", "photos"] }'
Only title, description, and tags are writeable. To change your discovery URL you must DELETE the existing listing and re-register from the new host.Withdraw the listing (soft-delete; the record stays so mirrors converge):
curl -s -X DELETE https://registry.afauth.org/v1/listings/did:web:api.example.com \
  -H 'Authorization: Bearer sess_T1uvWxYz3456_AbCdEfGhIj'

What if I lose the session token?

Repeat steps 1–3 with the same discovery_url. The directory recognises that a listing already exists for that DID, revokes all prior session tokens, and issues you a fresh one. The recovery path is identical to the initial flow: regaining host control regains the listing. This is the §4.3 re-challenge path.

What if someone hostile gets host control?

If your discovery host is compromised and the attacker submits a hostile listing, the same recovery mechanism works the moment you regain control. Re-challenge from the recovered host, and all prior sessions are revoked — including the attacker’s. The directory’s authority model is exactly the host’s authority. There is no second account, no recovery email, no support channel that can override host control.

How the directory keeps your listing fresh

By default, the directory re-fetches your discovery document once a day. After three consecutive revalidation failures (i.e., three days in a row at the default cadence), the listing transitions to stale status. Stale listings remain visible (with status: "stale") for 30 days before being soft-deleted. A successful fetch at any point resets the failure counter. You don’t need to do anything — just keep /.well-known/afauth reachable and valid. If you take it down deliberately, use DELETE to soft-delete; the stale-then-deleted path is for unintended outages. See §7 revalidation for the full state machine.

Rate limits

The directory enforces some abuse limits per IP, listed in the API reference. For ordinary registration and management, you won’t notice them. They only kick in if something is trying to spam the directory.

Errors you might hit

CodeMeaningFix
invalid_challengeChallenge expired (>30 min old) or already usedRequest a fresh challenge.
proof_fetch_failedThe directory couldn’t reach proof_urlCheck the URL is publicly reachable over HTTPS; check your firewall.
proof_mismatchThe body at proof_url does not equal the tokenEnsure the file contains only the token — no surrounding whitespace, JSON, or HTML.
discovery_fetch_failedThe directory couldn’t reach your discovery URLSame as above, applied to /.well-known/afauth.
discovery_invalidYour discovery document failed schema validationSee schemas/well-known.json.
discovery_did_mismatchYour discovery document’s service_did (a did:web:host) does not match the discovery hostEither correct the DID, or move discovery to the host the DID names.
conflictA different listing already claims this host or DIDIf it’s a stale entry of yours, re-challenge to take it back; otherwise contact the operator.
Every error response uses a structured envelope:
{
  "error": {
    "code":    "proof_mismatch",
    "message": "Proof body does not match challenge token"
  }
}
See Error codes for the full envelope contract.

Where to next

API reference

All endpoints, request and response shapes, query parameters.

What the directory is

Background, governance, federation, identity model.