> ## Documentation Index
> Fetch the complete documentation index at: https://agenticadvertisingorg-snap-format-preview-links.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AAO Directory API — Agent ↔ Publisher Inverse Lookup

> HTTP API for sales-agent operators to discover which publishers authorize their agent, sourced from the AAO directory's index of publisher adagents.json files. Returns provenance, per-publisher property counts, and lifecycle status.

# 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](https://cafemedia.com/.well-known/adagents.json) 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

| Parameter | Type             | Default      | Semantics                                                                                                                                                                                                                                                                                                            |
| --------- | ---------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `since`   | ISO 8601         | unset        | Return only publishers whose `last_verified_at` ≥ `since`. Enables incremental sync.                                                                                                                                                                                                                                 |
| `cursor`  | opaque string    | unset        | Pagination cursor returned by a prior response. Stable across the directory's refresh cycle for the lifetime of the cursor.                                                                                                                                                                                          |
| `status`  | string, repeated | `authorized` | Filter 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.             |
| `limit`   | int (1–1000)     | 200          | Max publishers per page.                                                                                                                                                                                                                                                                                             |
| `include` | string, repeated | unset        | Opt into expanded per-row fields. v1: `properties` — each `PublisherEntry` carries the canonical `property_ids[]` list under that publisher (lets consumers run full set-diff against a federated fetch, not just count comparison). Repeated-key form, same encoding rule as `status`. Unknown values return `400`. |

#### 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:

```ts theme={null}
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`):

```python theme={null}
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:

```yaml theme={null}
- 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.

#### Worked example: requesting `?include=properties` for full set-diff

```
GET /v1/agents/https%3A%2F%2Fsales-agent.example.com%2F/publishers?include=properties
```

The default response carries `properties_authorized` as a count only. Count-equality is **not** set-equality: a publisher rotating three properties leaves the count unchanged but the set entirely different, which a count-based divergence detector cannot see. `?include=properties` adds a `property_ids: list[string]` field per `PublisherEntry` — the canonical IDs the agent's selectors resolve to under that publisher — so consumers can run full set-diff against a federated `fetch_agent_authorizations` result and detect rotation, not just delta-in-magnitude.

The flag is opt-in to keep the default page payload small. Inline IDs add roughly the per-publisher property count × \~16 bytes per ID; on a managed-network parent file (\~6,800 publishers × avg 1 property each ≈ 7 KB of additional IDs), the overhead is small but non-zero. Pagination semantics are unchanged.

### Response

```json theme={null}
{
  "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..."
}
```

With `?include=properties`, each `PublisherEntry` additionally carries `property_ids`:

```json theme={null}
{
  "publisher_domain": "recipeswithessentialoils.com",
  "discovery_method": "ads_txt_managerdomain",
  "manager_domain": "cafemedia.com",
  "properties_authorized": 3,
  "properties_total": 3,
  "property_ids": ["p-001", "p-002", "p-003"],
  "signing_keys_pinned": false,
  "status": "authorized",
  "last_verified_at": "2026-05-19T08:00:00Z"
}
```

## Field reference

### Envelope

| Field                  | Required | Notes                                                                                                                                                                                                                |
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_url`            | yes      | Canonicalized echo of the lookup key.                                                                                                                                                                                |
| `directory_indexed_at` | yes      | Most 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. |
| `publishers`           | yes      | Array. Empty array is a valid response — the directory has indexed this agent but no current authorizations resolve.                                                                                                 |
| `next_cursor`          | optional | Opaque pagination cursor; absent or null on the terminal page.                                                                                                                                                       |

### `PublisherEntry`

| Field                   | Required    | Notes                                                                                                                                                                                                                                                                                                                                                                                          |
| ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publisher_domain`      | yes         | Publisher whose `adagents.json` authorizes the agent.                                                                                                                                                                                                                                                                                                                                          |
| `discovery_method`      | yes         | `direct`, `authoritative_location`, `adagents_authoritative`, or `ads_txt_managerdomain`. See below.                                                                                                                                                                                                                                                                                           |
| `manager_domain`        | conditional | Required when `discovery_method` ≠ `direct`. Null otherwise.                                                                                                                                                                                                                                                                                                                                   |
| `properties_authorized` | yes         | Count of properties under **this publisher\_domain only** that the agent's selectors resolve to. Never a network-wide count.                                                                                                                                                                                                                                                                   |
| `properties_total`      | yes         | Count 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.                                                                                                                                                                                                                                |
| `property_ids`          | conditional | Present iff request included `?include=properties`. Canonical list of `property_id`s under this publisher that the agent's selectors resolve to — the same population `properties_authorized` counts, surfaced as IDs so consumers can run full set-diff (not just count comparison) against a federated fetch. Per-publisher scope; never network-wide. Treat as a set; order is unspecified. |
| `signing_keys_pinned`   | optional    | Whether 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.                                                                                                                                                                                                                        |
| `status`                | yes         | `authorized` or `revoked`. See below.                                                                                                                                                                                                                                                                                                                                                          |
| `last_verified_at`      | yes         | When the directory last fetched and validated this publisher's `adagents.json`.                                                                                                                                                                                                                                                                                                                |

### `discovery_method` values

| Value                    | Meaning                                                                                                                                                                               | Trust profile                                                                                                                                             |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `direct`                 | Agent listed in the publisher's own `/.well-known/adagents.json`.                                                                                                                     | Strongest — no delegation hops.                                                                                                                           |
| `authoritative_location` | Publisher's `/.well-known/adagents.json` declared `authoritative_location` pointing to a manager file that lists the agent.                                                           | Strong — publisher actively delegated.                                                                                                                    |
| `adagents_authoritative` | Discovered via the manager file's own `properties[]` carrying the publisher's domain (per [adcp#4825 inline resolution rule](https://github.com/adcontextprotocol/adcp/issues/4825)). | Medium — publisher named in manager file but didn't host the delegation themselves.                                                                       |
| `ads_txt_managerdomain`  | Discovered via the publisher's `ads.txt` `MANAGERDOMAIN=` directive pointing to the manager file.                                                                                     | Weakest — the [`managerdomain` fallback safety rule](/docs/governance/property/adagents#safety-rules-for-this-fallback) 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

| Value        | Meaning                                                                                                                                                                                                                                                                                                    |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `authorized` | Selector resolves to ≥ 1 property under this publisher\_domain. The normal case.                                                                                                                                                                                                                           |
| `revoked`    | Publisher 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

| Status                  | Meaning                                                                                                                                                                                            |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`                | Lookup succeeded. Body MAY have empty `publishers[]`.                                                                                                                                              |
| `400 Bad Request`       | Malformed `agent_url`, invalid cursor, unknown `status` value, or `status` supplied as a comma-separated list rather than repeated keys.                                                           |
| `404 Not Found`         | Directory 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 Requests` | Rate limit. `Retry-After` header set. Bucket key: `agent_url` (anonymous) plus IP (defense-in-depth).                                                                                              |
| `5xx`                   | Directory 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](https://datatracker.ietf.org/doc/html/rfc9421) 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:

| Question                                                      | Primitive                                                  | Direction                         |
| ------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------- |
| 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}/publishers`**                | **Pull, 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.
4. Operators running a divergence detector (catching cases where the directory and the publisher's live `adagents.json` disagree) SHOULD request `?include=properties` and compare the directory's `property_ids[]` against a federated fetch as a set, not as a count. A publisher rotating N properties produces equal counts on both sides; only set-comparison catches it.

## Relationship to `publisher_properties` inline resolution

On managed-network-shape parent files (per [adcp#4825 inline resolution rule](/docs/governance/property/adagents#resolution-paths)), 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.
* **Full property objects inline.** `?include=properties` returns the resolved `property_ids[]` only — not the property objects themselves. Consumers with the IDs can fetch detail via existing per-domain primitives.

## See also

* [adagents.json Tech Spec](/docs/governance/property/adagents) — the trust root.
* [Managed Network Deployment](/docs/governance/property/managed-networks) — the canonical multi-publisher pattern this endpoint indexes.
* [adcp#4825](https://github.com/adcontextprotocol/adcp/issues/4825) — `publisher_properties` inline resolution rule the directory's count fields depend on.
