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.

The AgenticAdvertising.org registry provides a public REST API for resolving brands and properties, discovering agents, and validating authorization in the AdCP ecosystem.

Base URL

https://agenticadvertising.org
Most endpoints are public and require no authentication. Authenticated endpoints require a Bearer token. The full OpenAPI 3.1 specification is available for code generation and tooling. It is also discoverable at /.well-known/openapi.yaml.

Quick Start

Resolve a brand domain to its canonical identity:
curl "https://agenticadvertising.org/api/brands/resolve?domain=acmecorp.com"
Response
{
  "canonical_id": "acmecorp.com",
  "canonical_domain": "acmecorp.com",
  "brand_name": "Acme Corp",
  "keller_type": "master",
  "house_domain": "acmecorp.com",
  "source": "brand_json"
}

Rate Limits

EndpointLimit
Bulk resolve (/api/brands/resolve/bulk, /api/properties/resolve/bulk)20 requests/minute per IP
Save endpoints (/api/brands/save, /api/properties/save)60 requests/hour per user
Crawl request (/api/registry/crawl-request)5 minutes per domain, 30 requests/hour per user
All other endpointsNo limit
Rate-limited endpoints return 429 Too Many Requests when the limit is exceeded.

Endpoint Groups

Brand Resolution

Resolve domains to canonical brand identities, fetch brand.json files, and browse the brand registry.

Property Resolution

Resolve publisher domains to property information, validate adagents.json, and browse properties.

Agent Discovery

List, search, and filter agents by inventory profile. Browse publishers and view registry statistics.

Change Feed

Poll a cursor-based feed of registry changes for local sync.

Lookups & Authorization

Look up agents by domain, validate product authorization, and check property authorization in real time.

Lookups by entity

Three endpoints answer different questions about who is in the registry. They span two tag groups in the API reference, so the table below is the entry point.
EndpointQuestion it answers
GET /api/registry/agents”Give me the catalog.”
GET /api/registry/operator?domain=X”What does this entity operate?”
GET /api/registry/publisher?domain=X”What does this entity publish?”
GET /api/registry/agents β€” the catalog of AAO-attested member-enrolled agents in the registry. Use this when you want to browse or filter the public agent population. See Registering an agent for how agents end up in this catalog. Reference: List agents endpoint under Agent Discovery. GET /api/registry/operator?domain=X β€” given a domain, returns the agents that entity operates and the publishers that trust them. Use this when you have one entity in hand and want its agent footprint. Response shape is auth-aware: anonymous callers see only public agents, AAO API-tier callers also see members_only, and profile owners additionally see private (server/src/routes/registry-api.ts:5486). Reference: Operator lookup under Authorization Lookups. GET /api/registry/publisher?domain=X β€” given a domain, returns the inventory that entity publishes (properties[]) and which agents it authorizes (authorized_agents[] from its adagents.json). Use this when you have one publisher in hand and want its inventory and delegations. The endpoint is unauthenticated and returns the same shape for every caller (server/src/routes/registry-api.ts:5550). Reference: Publisher lookup under Authorization Lookups.

Brand Resolution

These endpoints resolve domains to brand identities. The source field in the response indicates where the data came from:
SourceMeaning
brand_jsonResolved from the domain’s /.well-known/brand.json file
enrichedEnriched via Brandfetch API
communitySubmitted by a community member
All sources produce the same resolution response structure. To get full brand identity data (logos, colors, tone), use /api/brands/enrich or look up the brand in the registry.
MethodPathDescription
GET/api/brands/resolveResolve a domain to its canonical brand
POST/api/brands/resolve/bulkResolve up to 100 domains at once
GET/api/brands/brand-jsonFetch raw brand.json for a domain
GET/api/brands/registryList all brands (search, pagination)
GET/api/brands/enrichEnrich brand data via Brandfetch
GET/api/brands/historyEdit history for a brand
POST/api/brands/saveSave or update a community brand (auth required)

Property Resolution

MethodPathDescription
GET/api/properties/resolveResolve a domain to its property info
POST/api/properties/resolve/bulkResolve up to 100 domains at once
GET/api/properties/registryList all properties (search, pagination)
GET/api/properties/validateValidate a domain’s adagents.json
GET/api/properties/historyEdit history for a property
POST/api/properties/saveSave or update a hosted property (auth required)

Agent Discovery

MethodPathDescription
GET/api/registry/agentsList all member-enrolled agents β€” filter by type (`?type=brandrightsmeasurementgovernancecreativesalesbuyingsignals`)
GET/api/registry/agents/searchSearch agents by inventory profile (auth required)
GET/api/registry/publishersList all publishers
GET/api/registry/statsRegistry statistics
POST/api/registry/crawl-requestRequest re-crawl of a publisher domain (auth required)

Measurement-vendor discovery

To discover measurement vendors specifically (Adelaide-style attention, Scope3-style emissions, Nielsen DAR, IAS/DV custom quality, etc.), filter the agent list by type=measurement:
curl "https://agenticadvertising.org/api/registry/agents?type=measurement"
This returns every measurement agent enrolled in the registry by an AAO member. Per-metric catalog discovery. Each measurement agent publishes its full per-metric catalog at get_adcp_capabilities.measurement.metrics[] β€” the canonical, vendor-controlled source of truth. AAO crawls each measurement agent’s get_adcp_capabilities on a TTL and stores the result; passing ?capabilities=true folds the catalog into the response next to creative_capabilities and signals_capabilities. Filter parameters (all imply type=measurement when present; an explicit non-measurement type returns 400):
ParamMatchNotes
metric_id=attention_unitsExact match on metrics[].metric_idRepeatable; multiple values are OR’d within the param.
accreditation=MRCExact match on metrics[].accreditations[].accrediting_bodyRepeatable. Vendor-asserted β€” verified_by_aao is always false in the response; renderers should mark these as vendor claims, not AAO endorsements.
q=attentionCase-insensitive substring on metric_idv1 scope: metric_id only. Max 64 chars; SQL wildcards (%, _) rejected. Description/standard fuzzy search is a follow-up.
# All vendors offering attention measurement
curl "https://agenticadvertising.org/api/registry/agents?metric_id=attention_units&capabilities=true"

# MRC-accredited viewability vendors
curl "https://agenticadvertising.org/api/registry/agents?type=measurement&accreditation=MRC&q=viewab&capabilities=true"
Direct call vs. index β€” when to use which. Buyers have two paths to a vendor’s metric catalog:
Use casePathWhy
Discovery / planning (β€œwhich vendors offer attention?”)AAO index (?metric_id=...)Pre-aggregated, cached, fast. Cross-vendor in one call. Stale up to the AAO TTL window (typically 24h).
Settlement / audit (β€œdoes Adelaide currently support metric X?”)Direct call to get_adcp_capabilitiesLive, canonical, no staleness. The buyer is already calling the measurement agent at delivery / reconciliation time; one extra call is cheap and removes index staleness from the audit trail.
Filtering at get_products timeAAO indexBuyer is in a fast-path query and the seller’s product catalog already needs to know which vendors are valid.

Change Feed

MethodPathDescription
GET/api/registry/feedPoll cursor-based registry change feed (auth required)

Lookups & Authorization

MethodPathDescription
GET/api/registry/lookup/domain/{domain}Find agents authorized for a domain
GET/api/registry/lookup/propertyFind agents by property identifier
GET/api/registry/lookup/agent/{agentUrl}/domainsGet all domains for an agent
POST/api/registry/validate/product-authorizationValidate agent product authorization
POST/api/registry/expand/product-identifiersExpand property selectors to identifiers
GET/api/registry/validate/property-authorizationReal-time authorization check
Authorization validation checks both sides: the publisher’s adagents.json (does it authorize this agent with the claimed delegation_type?) and the operator’s brand.json (does it declare this property with a matching relationship?).

Validation Tools

MethodPathDescription
POST/api/adagents/validateValidate adagents.json for a domain
POST/api/adagents/createGenerate adagents.json content
MethodPathDescription
GET/api/searchSearch across brands, publishers, and properties
GET/api/manifest-refs/lookupFind manifest references for a domain

Agent Probing

MethodPathDescription
GET/api/public/discover-agentProbe an agent URL for capabilities
GET/api/public/agent-formatsGet creative formats from an agent
GET/api/public/agent-productsGet products from a sales agent
GET/api/public/validate-publisherValidate a publisher domain

Activity history

GET /api/brands/history?domain={domain} and GET /api/properties/history?domain={domain} return the edit history for a registry entry, newest first. These are public endpoints β€” no authentication required.
Response
{
  "domain": "acmecorp.com",
  "total": 3,
  "revisions": [
    {
      "revision_number": 3,
      "editor_name": "Pinnacle Media",
      "edit_summary": "Updated logo URL",
      "source": "community",
      "is_rollback": false,
      "created_at": "2026-03-01T12:34:56Z"
    },
    {
      "revision_number": 2,
      "editor_name": "system",
      "edit_summary": "API: enriched via Brandfetch",
      "source": "enriched",
      "is_rollback": false,
      "created_at": "2026-02-15T08:00:00Z"
    }
  ]
}
Entries with editor_name: "system" were written by automated enrichment. When is_rollback is true, rolled_back_to contains the revision number that was restored. Pagination uses limit (max 100) and offset query parameters.

Anti-abuse and anti-homograph controls

Because /api/brands/save, /api/properties/save, and the adagents validation endpoints accept domain strings from authenticated member organizations, the hosted registry applies a layered floor of anti-abuse controls at save time. These are operational behaviors of the AgenticAdvertising.org registry in the 3.x era β€” not a new wire surface β€” and exist so that typosquats, confusable lookalikes, and drive-by brand hijacks cannot get written into the index by a single authenticated caller.
  • Domain normalization (IDNA 2008 + confusable detection). Save endpoints SHOULD apply IDNA 2008 to normalize internationalized domain names to ASCII before persistence, and SHOULD then run Unicode confusable-detection (for example, ICU uspoof or equivalent) against two corpora: (1) already-registered entries in the index, and (2) a curated high-value-brand deny list maintained by the registry operator. The deny list catches typosquats of well-known brands before those brands themselves are registered (e.g., a g00gle.com submission collides with the deny-list entry even if Google has not yet claimed an index row). Ambiguous submissions β€” mixed-script labels, homograph collisions, disallowed Unicode classes β€” SHOULD be rejected or flagged for human review rather than silently committed.
  • Ownership proof before commit. Save endpoints MUST require evidence of domain control before a new brand or property entry is committed to the index β€” this is the threat that motivates the control, since an attacker with a compromised member API key would otherwise be free to bulk-register fresh confusable variants that have no prior entry to conflict with. For revisions to an existing community-source entry by the same authenticated organization, re-proof SHOULD be required on a rolling basis (for example, once the prior proof is older than 90 days) but MAY be skipped within that window. Accepted proofs are either a DNS TXT record at _adcp-owner.{domain} matching a server-issued nonce, or an HTTP challenge hosted at /.well-known/adcp-ownership.txt on the domain. Nonces MUST be single-use, scoped to the (organization, domain) pair, and MUST expire within 15 minutes of issuance; verification MUST consume the nonce on success and invalidate it on failure. A leaked or unused nonce after expiry is dead. Revisions to an existing authoritative entry (i.e., one backed by brand.json / adagents.json) continue to follow the 409 Conflict semantics in Save brand and Save property; ownership proof covers the community-source save path.
  • Per-organization rate limits on saves. In addition to the per-IP rate limits documented in Rate Limits, save endpoints SHOULD apply per-organization limits so that a single compromised API key cannot bulk-register confusable variants. The hosted implementation uses a burst-tolerant cap (indicative: tens of saves per hour per org, low-hundreds per day per org); callers exceeding the per-org bucket receive 429 Too Many Requests.
These controls are enforced by the hosted AgenticAdvertising.org registry. Self-hosted mirrors consuming the Change Feed rely on the hosted registry’s save-time checks and do not re-run them β€” which is consistent with the feed’s advisory-identity posture (see specs/registry-change-feed.md Β§Advisory identity material): the feed is change-detection, not a trust anchor, and the publisher’s own adagents.json pin remains the authoritative identity source (see adagents.json Β§signing_keys). Operators running an alternative registry implementation SHOULD apply equivalent save-time controls before accepting community-source writes.

Authentication

Public endpoints (resolution, discovery, search) require no authentication. Write endpoints accept either an organization API key (server-to-server) or a user JWT obtained via OAuth 2.1 (interactive / agent clients). Both are sent in the Authorization: Bearer ... header.

Option A: Organization API key

Long-lived, org-scoped. Best for server-to-server integrations where no user is present.
  1. Sign in at agenticadvertising.org/dashboard/api-keys
  2. Click Create key and copy the generated key
Pass the key in the Authorization header:
Authorization: Bearer sk_...

Option B: User SSO via OAuth 2.1

Short-lived, user-scoped. Best for agent clients (MCP, AI assistants, custom apps) where a human is signing in to AAO. A single token works against both /mcp and the REST API. Discovery follows RFC 8414 and RFC 9728:
  • Authorization server metadata: GET /.well-known/oauth-authorization-server
  • Protected-resource metadata (REST API): GET /.well-known/oauth-protected-resource/api
  • Protected-resource metadata (MCP): GET /.well-known/oauth-protected-resource/mcp
The flow is authorization code with PKCE. Dynamic client registration (RFC 7591) is available at /register. Users authenticate via AuthKit; the token is a WorkOS-signed JWT.
Authorization: Bearer <jwt>
A valid user JWT proves identity, not entitlement. Endpoints gated on organization membership or admin role (most write endpoints) still return 403 if the authenticated user lacks the required standing.

Authenticated endpoints

These endpoints require a valid API key.

Save brand

POST /api/brands/save Save or update a community brand in the registry. For existing brands, creates a revision-tracked edit. Cannot edit authoritative brands managed via brand.json β€” those return 409 Conflict. Request body:
{
  "domain": "acmecorp.com",
  "brand_name": "Acme Corp",
  "brand_manifest": {
    "name": "Acme Corp",
    "description": "A fictional company",
    "logos": [{ "url": "https://acmecorp.com/logo.svg", "tags": ["icon"] }],
    "colors": [{ "hex": "#FF5733", "type": "accent" }]
  }
}
domain and brand_name are required. brand_manifest (brand identity data) is optional. The brand’s source is set to "community" by the server. Domains are normalized (protocol stripped, lowercased).
curl -X POST "https://agenticadvertising.org/api/brands/save" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"acmecorp.com","brand_name":"Acme Corp"}'
Response (create)
{
  "success": true,
  "message": "Brand \"Acme Corp\" saved to registry",
  "domain": "acmecorp.com",
  "id": "br_abc123"
}
Response (update)
{
  "success": true,
  "message": "Brand \"Acme Corp\" updated in registry (revision 2)",
  "domain": "acmecorp.com",
  "id": "br_abc123",
  "revision_number": 2
}

Save property

POST /api/properties/save Save or update a hosted property in the registry. For existing properties, creates a revision-tracked edit. Cannot edit authoritative properties managed via adagents.json β€” those return 409 Conflict. Request body:
{
  "publisher_domain": "examplepub.com",
  "authorized_agents": [
    { "url": "https://agent.example.com", "authorized_for": "sell" }
  ],
  "properties": [
    { "type": "website", "name": "Example Publisher" }
  ],
  "contact": {
    "name": "Ad Ops",
    "email": "adops@examplepub.com"
  }
}
publisher_domain and authorized_agents (each with a required url and optional authorized_for) are required. properties (each requiring type and name) and contact are optional. Domains are normalized (protocol stripped, lowercased).
curl -X POST "https://agenticadvertising.org/api/properties/save" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "publisher_domain": "examplepub.com",
    "authorized_agents": [{"url": "https://agent.example.com", "authorized_for": "sell"}],
    "properties": [{"type": "website", "name": "Example Publisher"}]
  }'
Response (create)
{
  "success": true,
  "message": "Hosted property created for examplepub.com",
  "id": "prop_xyz789"
}
Response (update)
{
  "success": true,
  "message": "Property 'examplepub.com' updated (revision 2)",
  "id": "prop_xyz789",
  "revision_number": 2
}

Change feed

GET /api/registry/feed Poll a cursor-based feed of registry changes. Use this to keep a local copy of the registry in sync without re-fetching the full dataset. Events are ordered by UUID v7 event_id, providing monotonic cursor progression. The feed retains events for 90 days β€” expired cursors return 410 Gone. Query parameters:
ParameterTypeDefaultDescription
cursorUUIDβ€”Resume after this event ID. Omit for the earliest available events.
typesstringβ€”Comma-separated event type filters. Supports glob patterns (e.g. property.*).
limitnumber100Max events per page (1–10,000).
Event types:
TypeDescription
property.createdA new property was added to the registry
property.updatedProperty metadata changed
property.mergedTwo property records were merged
property.staleProperty failed re-crawl validation
property.reactivatedA stale property passed re-crawl
agent.discoveredA new agent_url appeared in a publisher’s adagents.json (authorization graph; does not imply the agent is in /api/registry/agents)
agent.removedAn agent was removed from the registry
agent.profile_updatedAgent inventory profile changed
publisher.adagents_changedA publisher’s adagents.json was updated
authorization.grantedAn agent was authorized for a property
authorization.revokedAn authorization was removed
curl "https://agenticadvertising.org/api/registry/feed?types=property.*&limit=50" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "events": [
    {
      "event_id": "019539a0-1234-7000-8000-000000000001",
      "event_type": "property.created",
      "entity_type": "property",
      "entity_id": "019539a0-b1c2-7000-8000-000000000002",
      "payload": {},
      "actor": "crawler",
      "created_at": "2026-03-31T10:00:00.000Z"
    }
  ],
  "cursor": "019539a0-1234-7000-8000-000000000001",
  "has_more": true
}
When has_more is true, pass the returned cursor value in the next request to continue polling. When false, you’ve reached the end of the current feed β€” poll again later with the same cursor to pick up new events. If the cursor has expired (older than 90 days or not found), the response is 410 Gone:
410 Gone
{
  "error": "cursor_expired",
  "message": "Cursor is older than 90-day retention window. Re-bootstrap from /registry/agents/search and /catalog/sync."
}
GET /api/registry/agents/search Search agents by inventory profile β€” channels, markets, content categories, property types, and more. Filters use AND across dimensions and OR within a dimension. Results are ranked by a relevance score based on filter match breadth, inventory depth, and TMP support. Query parameters:
ParameterTypeDefaultDescription
channelsCSVβ€”Filter by channel (e.g. ctv,olv,display)
property_typesCSVβ€”Filter by property type (e.g. ctv_app,website)
marketsCSVβ€”Filter by market/country code (e.g. US,GB)
categoriesCSVβ€”Filter by IAB content category (e.g. IAB-7,IAB-7-1)
tagsCSVβ€”Filter by tag (e.g. premium,brand_safe)
delivery_typesCSVβ€”Filter by delivery type (e.g. guaranteed,programmatic)
has_tmpbooleanβ€”Require TMP support (true or false)
min_propertiesnumberβ€”Minimum number of properties in inventory
cursorstringβ€”Pagination cursor from a previous response
limitnumber50Max results per page (1–200)
Each CSV parameter accepts up to 100 values.
curl "https://agenticadvertising.org/api/registry/agents/search?channels=ctv,olv&markets=US&has_tmp=true" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "results": [
    {
      "agent_url": "https://ads.streamhaus.example.com",
      "channels": ["ctv", "olv"],
      "property_types": ["ctv_app", "website"],
      "markets": ["US", "GB", "CA"],
      "categories": ["IAB-7", "IAB-7-1"],
      "tags": ["premium"],
      "delivery_types": ["guaranteed"],
      "format_ids": [],
      "property_count": 42,
      "publisher_count": 3,
      "has_tmp": true,
      "category_taxonomy": null,
      "relevance_score": 0.92,
      "matched_filters": ["channels", "markets"],
      "updated_at": "2026-03-31T10:00:00.000Z"
    }
  ],
  "cursor": "MC45Mjpodh...",
  "has_more": false
}
The matched_filters array shows which filter dimensions matched, useful for understanding why a result was returned. The relevance_score combines filter match breadth, ln(property_count + 1) weighted at 0.1, and a 0.05 boost for TMP support.

Crawl request

POST /api/registry/crawl-request Request an immediate re-crawl of a publisher domain. Use this after updating an adagents.json file so the registry picks up changes without waiting for the next scheduled crawl. The crawl runs asynchronously β€” the endpoint returns 202 Accepted immediately. Rate-limited to one request per domain every 5 minutes and 30 requests per user per hour. Request body:
{
  "domain": "examplepub.com"
}
domain is required. Domains are normalized (lowercased, trimmed). The endpoint validates the domain format and performs a DNS lookup to reject private/reserved IP addresses.
curl -X POST "https://agenticadvertising.org/api/registry/crawl-request" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"examplepub.com"}'
202 Accepted
{
  "message": "Crawl request accepted",
  "domain": "examplepub.com"
}
429 Too Many Requests
{
  "error": "Rate limit exceeded for this domain",
  "retry_after": 245
}
retry_after is the number of seconds to wait before retrying.

Submit brand (legacy)

POST /api/brands/discovered/community Submit a brand for review. This endpoint predates /api/brands/save β€” prefer the save endpoint for new integrations.

Error responses

StatusDescription
400Missing required fields or invalid domain
401Missing or invalid API key
409Cannot edit an authoritative brand/property (managed via brand.json or adagents.json)
410Cursor expired (change feed β€” older than 90-day retention window)
429Rate limit exceeded

Protocol vs REST API

The AdCP protocol defines MCP and A2A tasks for agent-to-agent communication (e.g. get_products, create_media_buy). The registry REST API is separate β€” it provides HTTP endpoints for looking up entities in the AgenticAdvertising.org registry. Use the REST API for discovery and authorization:
  • Resolve brand or property domains before making protocol calls
  • Discover which agents exist and what they’re authorized for
  • Validate authorization in real time during ad serving
  • Build integrations that browse or search the registry
Use MCP/A2A tasks for transactional operations:
  • Fetching products from a sales agent (get_products)
  • Creating media buys (create_media_buy)
  • Building creatives (build_creative)
  • Getting signals (get_signals)
A typical integration uses both: resolve a publisher domain via the registry API, then call the authorized agent’s MCP endpoint to transact.