Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt

Use this file to discover all available pages before exploring further.

AAO Directory API

The AAO directory at agenticadvertising.org indexes publisher adagents.json files across the open web. This API surfaces the inverse map every sales-agent operator needs at sync time:
“What publishers have authorized my agent?”
Without this endpoint, operators have to either maintain the publisher domain list manually and call fetch_agent_authorizations against it, or crawl the open web themselves. Both are infeasible at managed-network scale (cafemedia alone delegates ~6,800 publisher domains under a single manager file). This endpoint is discovery, not authorization. The publisher’s own adagents.json remains the trust root. The directory tells you which publishers to verify directly via the SDK’s per-domain primitives (verify_agent_authorization, fetch_agent_authorizations).

Endpoint

GET https://{aao_directory}/v1/agents/{agent_url}/publishers
{agent_url} MUST be percent-encoded. The directory canonicalizes the lookup key (lowercase host, default port stripped, trailing slash on path component normalized) using the same convention the SDK applies in verify_agent_authorization.

Query parameters

ParameterTypeDefaultSemantics
sinceISO 8601unsetReturn only publishers whose last_verified_atsince. Enables incremental sync.
cursoropaque stringunsetPagination cursor returned by a prior response. Stable across the directory’s refresh cycle for the lifetime of the cursor.
statusstring, repeatedauthorizedFilter by lifecycle status. v1: authorized, revoked. Repeat the key once per value (OpenAPI style: form, explode: true). Comma-separated single-value form (status=authorized,revoked) is not accepted; directories MUST return 400 with an explanation pointing to the repeated-key form.
limitint (1–1000)200Max publishers per page.

Worked example: filtering by multiple status values

GET /v1/agents/https%3A%2F%2Fsales-agent.example.com%2F/publishers?status=authorized&status=revoked&limit=500
Equivalent in TypeScript:
const url = new URL(`${directory}/v1/agents/${encodeURIComponent(agentUrl)}/publishers`);
url.searchParams.append("status", "authorized");
url.searchParams.append("status", "revoked");
url.searchParams.set("limit", "500");
Equivalent in Python (requests):
requests.get(
    f"{directory}/v1/agents/{quote(agent_url, safe='')}/publishers",
    params=[("status", "authorized"), ("status", "revoked"), ("limit", "500")],
)
The OpenAPI fragment for the status parameter:
- in: query
  name: status
  schema:
    type: array
    items:
      type: string
      enum: [authorized, revoked]
  style: form
  explode: true
  required: false
Repeated-key was chosen because (a) it is what URLSearchParams.append() and OpenAPI’s default explode: true produce, (b) it composes cleanly with future values that might contain a comma, and (c) it leaves no parser ambiguity at the directory.

Response

{
  "agent_url": "https://sales-agent.example.com",
  "directory_indexed_at": "2026-05-19T12:00:00Z",
  "publishers": [
    {
      "publisher_domain": "recipeswithessentialoils.com",
      "discovery_method": "ads_txt_managerdomain",
      "manager_domain": "cafemedia.com",
      "properties_authorized": 1,
      "properties_total": 1,
      "signing_keys_pinned": false,
      "status": "authorized",
      "last_verified_at": "2026-05-19T08:00:00Z"
    },
    {
      "publisher_domain": "wsj.com",
      "discovery_method": "direct",
      "manager_domain": null,
      "properties_authorized": 47,
      "properties_total": 200,
      "signing_keys_pinned": true,
      "status": "authorized",
      "last_verified_at": "2026-05-19T10:00:00Z"
    },
    {
      "publisher_domain": "former-partner.example",
      "discovery_method": "authoritative_location",
      "manager_domain": "cafemedia.com",
      "properties_authorized": 0,
      "properties_total": 0,
      "signing_keys_pinned": false,
      "status": "revoked",
      "last_verified_at": "2026-05-19T11:00:00Z"
    }
  ],
  "next_cursor": "eyJv..."
}

Field reference

Envelope

FieldRequiredNotes
agent_urlyesCanonicalized echo of the lookup key.
directory_indexed_atyesMost recent per-publisher refresh in the result set. Provenance for the consumer’s own cache. NULL on empty pages — there’s no anchor to report; consumers SHOULD NOT advance cache freshness from a null value.
publishersyesArray. Empty array is a valid response — the directory has indexed this agent but no current authorizations resolve.
next_cursoroptionalOpaque pagination cursor; absent or null on the terminal page.

PublisherEntry

FieldRequiredNotes
publisher_domainyesPublisher whose adagents.json authorizes the agent.
discovery_methodyesdirect, authoritative_location, adagents_authoritative, or ads_txt_managerdomain. See below.
manager_domainconditionalRequired when discovery_methoddirect. Null otherwise.
properties_authorizedyesCount of properties under this publisher_domain only that the agent’s selectors resolve to. Never a network-wide count.
properties_totalyesCount of properties under this publisher_domain only in the publisher’s file (or parent file’s inline subset for that domain). Never a network-wide count.
signing_keys_pinnedoptionalWhether the publisher pins signing_keys[] on this agent. When true, agent’s signed responses MUST verify against the pinned set regardless of the agent’s own JWKS.
statusyesauthorized or revoked. See below.
last_verified_atyesWhen the directory last fetched and validated this publisher’s adagents.json.

discovery_method values

ValueMeaningTrust profile
directAgent listed in the publisher’s own /.well-known/adagents.json.Strongest — no delegation hops.
authoritative_locationPublisher’s /.well-known/adagents.json declared authoritative_location pointing to a manager file that lists the agent.Strong — publisher actively delegated.
adagents_authoritativeDiscovered via the manager file’s own properties[] carrying the publisher’s domain (per adcp#4825 inline resolution rule).Medium — publisher named in manager file but didn’t host the delegation themselves.
ads_txt_managerdomainDiscovered via the publisher’s ads.txt MANAGERDOMAIN= directive pointing to the manager file.Weakest — the managerdomain fallback safety rule is the only positive cross-check.
The directory verifies the managerdomain safety rule before returning a row with discovery_method: ads_txt_managerdomain — this is the directory’s main value-add over a per-operator ads.txt crawl.

status values

ValueMeaning
authorizedSelector resolves to ≥ 1 property under this publisher_domain. The normal case.
revokedPublisher previously authorized the agent and now lists this publisher_domain in revoked_publisher_domains[] of an authoritative file. Emitted as a tombstone on the first sync after revocation lands, then dropped. Lets operators propagate revocations without polling each publisher’s cache TTL.
unbound, pending, unreachable, and no_properties are intentionally not part of v1. The directory only indexes publishers whose adagents.json was successfully fetched and references the agent. If a publisher disappears, the directory drops it from results rather than returning a tombstone (consumers track membership via set-diff against prior pages).

HTTP semantics

StatusMeaning
200 OKLookup succeeded. Body MAY have empty publishers[].
400 Bad RequestMalformed agent_url, invalid cursor, unknown status value, or status supplied as a comma-separated list rather than repeated keys.
404 Not FoundDirectory has never indexed any publisher referencing this agent_url. Distinct from 200 + empty (which means the directory has indexed this agent, but no current authorizations resolve).
429 Too Many RequestsRate limit. Retry-After header set. Bucket key: agent_url (anonymous) plus IP (defense-in-depth).
5xxDirectory error. Consumer SHOULD retry with backoff.
The endpoint sets Cache-Control and ETag. Conditional GET (If-None-Match) is the wire-level cache mechanism; directory_indexed_at in the body is the freshness anchor for consumer logic.

Authentication

V1 is unauthenticated. Publisher adagents.json files are public; the inverse map is public. If rate-limiting graduates from IP-based to identity-based, the path is a separate RFC layering RFC 9421 request signing keyed off the agent’s published JWKS — the agent proves it controls agent_url by signing the request. Out of scope for this RFC.

Pagination

Cursors are opaque. Treat them as substitutable strings and pass them back verbatim. The directory MAY change cursor format without notice; consumers MUST NOT parse cursor contents. A cursor remains valid for at least one directory refresh cycle. Past that, the directory MAY return 400 with cursor_expired or 200 with re-traversal from the start — both are conforming. Consumers SHOULD record the wall-clock time of the prior request and refuse to use cursors older than 24 hours.

Relationship to other primitives

The AAO directory complements the existing SDK primitives:
QuestionPrimitiveDirection
Is this agent listed in this publisher’s adagents.json?verify_agent_authorization(adagents_data, agent_url)Push (publisher → agent)
Given a list of publishers, which authorize my agent?fetch_agent_authorizations(agent_url, publisher_domains)Pull, caller-supplied list
Which publishers authorize my agent?GET /v1/agents/{agent_url}/publishersPull, directory-supplied list
The first two answer questions where the operator already knows the publisher set. The directory endpoint answers the operator’s actual sync-time question: “what’s my publisher set?” The recommended workflow:
  1. Call GET /v1/agents/{agent_url}/publishers to discover the publisher set.
  2. For each publisher_domain in the response, the operator MAY call verify_agent_authorization against the publisher’s own adagents.json to re-confirm against the trust root. The directory’s last_verified_at reduces but does not eliminate the need for per-domain verification on critical paths.
  3. Use the response’s properties_authorized / properties_total for operator-facing scope summaries, and the signing_keys_pinned flag to surface which agents must publish a JWKS matching the publisher’s pin.

Relationship to publisher_properties inline resolution

On managed-network-shape parent files (per adcp#4825 inline resolution rule), the directory computes properties_total from the parent file’s inline properties[] filtered by publisher_domain. Strict federation at this scale would require N HTTP fetches per directory refresh per publisher — the same scale problem operators have, moved one layer up. The directory uses the inline resolution rule the spec endorses.

Out of scope (v1)

  • Authentication. Public endpoint, anonymous rate limiting. Identity-bound limits arrive in a separate RFC if needed.
  • Cross-directory federation. Single directory. The endpoint shape is defined such that multiple AAO-compatible directories could implement it; discovery of which directory to query is configuration today.
  • Push notification of new authorizations. Poll-based v1.
  • Inline property detail. properties_authorized / properties_total are counts only; full property lists arrive via a future ?include=properties if operators ask.

See also