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

# Understanding Authorization

> Authorized properties in AdCP let buyers restrict campaigns to verified inventory using property lists and supply path validation.

One of the foundational challenges in digital advertising is **unauthorized resale** - ensuring that sales agents are actually authorized to represent the advertising properties they claim to sell. AdCP solves this problem through a comprehensive authorization system that builds on the lessons learned from ads.txt in programmatic advertising.

> [!TIP]
>
> **New to AdCP authorization?** Read [AdCP Basics: Authorized Properties](https://bokonads.com/p/adcp-basics-authorized-properties) for an accessible introduction to how authorization works in agentic advertising.

## The Problem: Unauthorized Resale

### Historical Context

In programmatic advertising, the Ads.txt initiative was created to solve a critical problem: unauthorized reselling of advertising inventory. Before ads.txt, bad actors could claim to represent popular websites and sell their inventory without permission, leading to:

* **Revenue theft**: Publishers lost money to unauthorized sellers
* **Brand safety issues**: Buyers couldn't verify legitimate inventory sources
* **Market fragmentation**: No way to distinguish authorized from unauthorized sellers

### The Same Problem in AI-Powered Advertising

AdCP faces similar challenges as AI agents begin to buy and sell advertising programmatically:

* **AI sales agents** may claim to represent properties they don't actually control
* **Buyer agents** need to verify authorization before making purchases
* **Publishers** need a way to explicitly authorize specific sales agents
* **Scale challenges**: Manual verification doesn't work for networks with thousands of properties

## The Solution: AdCP Authorization System

AdCP prevents unauthorized resale through a three-part system:

1. **Publisher Authorization**: Publishers explicitly authorize sales agents via `adagents.json` with `delegation_type` (`direct`, `delegated`, or `ad_network`)
2. **Operator Declaration**: Operators declare their property portfolio in `brand.json` with the `relationship` field. First-party inventory uses `owned`; direct, delegated, or network paths use values that match `delegation_type`.
3. **Agent Discovery**: Sales agents declare their portfolio via [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities) in the `media_buy.portfolio` section

The first two create bilateral verification — like `ads.txt` + `sellers.json` in programmatic. For direct, delegated, or network paths, both sides must agree for the supply path to be trusted. See [ad networks](/dist/docs/3.3.0-beta.0/sponsored-intelligence/networks) for the full pattern.

## How Publishers Authorize Sales Agents

Publishers authorize sales agents by hosting an `adagents.json` file at `/.well-known/adagents.json` on their domain. This file lists all authorized agents and their permissions.

### Example adagents.json

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/3.3.0-beta.0/adagents.json",
  "contact": {
    "name": "Sports Network Media",
    "email": "adops@sportsnetwork.com",
    "domain": "sportsnetwork.com"
  },
  "properties": [
    {
      "property_id": "sports_network_main",
      "property_type": "website",
      "name": "Sports Network",
      "identifiers": [
        {"type": "domain", "value": "sportsnetwork.com"}
      ],
      "tags": ["premium", "sports"]
    }
  ],
  "authorized_agents": [
    {
      "url": "https://sports-media-sales.com",
      "authorized_for": "All Sports Network properties",
      "authorization_type": "property_tags",
      "property_tags": ["sports"],
      "delegation_type": "direct"
    },
    {
      "url": "https://premium-ad-network.com",
      "authorized_for": "Premium inventory only",
      "authorization_type": "property_tags",
      "property_tags": ["premium"],
      "delegation_type": "ad_network",
      "countries": ["US", "CA"]
    }
  ],
  "last_updated": "2025-01-10T12:00:00Z"
}
```

### Key Fields

* **contact**: Identifies the publisher/entity managing this file
* **properties**: Defines the properties covered by this authorization file
* **authorized\_agents**: List of sales agents authorized to represent properties
  * **url**: Agent's API endpoint URL
  * **authorized\_for**: Human-readable description of authorization scope
  * **authorization\_type**: How properties are selected (`property_ids`, `property_tags`, `inline_properties`, `publisher_properties`)
  * **delegation\_type**: Whether this path is `direct`, `delegated`, or `ad_network`
  * **collections**: Optional collection selectors that narrow authorization to specific content programs. The selector's `publisher_domain` may be an external channel owner's domain (owner-sold carriage), and a selector without `collection_ids` is a bulk grant for every collection declared at that domain — see [owner-sold channel carriage](/dist/docs/3.3.0-beta.0/governance/property/adagents#owner-sold-channel-carriage)
  * **placement\_ids**: Optional placement IDs from the publisher's placement registry in `adagents.json`
  * **countries**: Optional ISO 3166-1 alpha-2 country codes
  * **effective\_from / effective\_until**: Optional authorization window
  * **exclusive**: Whether this is the sole authorized path for the scoped inventory slice
* **last\_updated**: ISO 8601 timestamp of last modification

## How Sales Agents Share Authorized Properties

Sales agents use the [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities) task to declare their portfolio information in the `media_buy.portfolio` section. This serves multiple purposes:

1. **Transparency**: Buyers can see what publishers an agent represents
2. **Validation enablement**: Provides publisher domains for buyers to verify authorization via `adagents.json`
3. **Brief routing**: Present primary-channel and primary-country arrays are exhaustive allowlists for briefs the agent accepts. Omission means unknown scope, not global coverage.

The routing arrays do not guarantee inventory availability. Buyers may skip an
agent when a brief has no country/channel intersection, but matching scope still
requires `get_products` to resolve account access, dates, formats, policy, and
current inventory. Intermediaries MUST NOT present inferred upstream scope as if
the upstream sales agent declared it; they may advertise a narrower scope as
their own operator-confirmed routing constraint.

### Property Declaration Example

```json theme={null}
{
  "properties": [
    {
      "property_type": "website",
      "name": "Sports Network",
      "identifiers": [
        {"type": "domain", "value": "sportsnetwork.com"}
      ],
      "tags": ["sports_network", "premium"],
      "publisher_domain": "sportsnetwork.com"
    },
    {
      "property_type": "radio",
      "name": "WXYZ-FM Chicago",
      "identifiers": [
        {"type": "station_id", "value": "WXYZ-FM"},
        {"type": "facility_id", "value": "fcc:21242"}
      ],
      "tags": ["local_radio", "midwest", "chicago"],
      "publisher_domain": "radionetwork.com"
    }
  ],
  "tags": {
    "sports_network": {
      "name": "Sports Network Properties",
      "description": "145 sports properties and networks"
    },
    "local_radio": {
      "name": "Local Radio Stations",
      "description": "1847 local radio stations across US markets"
    }
  },
  "advertising_policies": "We maintain strict brand safety standards. Prohibited categories include: tobacco and vaping products, online gambling and sports betting, cannabis and CBD products, political advertising, and speculative financial products (crypto, NFTs, penny stocks).\n\nWe also prohibit misleading tactics such as clickbait headlines, false scarcity claims, hidden pricing, and ads targeting vulnerable populations.\n\nCompetitor brands in the streaming media space are blocked by policy.\n\nFull advertising guidelines: https://publisher.com/advertising-policies"
}
```

### Property Tags for Scale

For large networks representing thousands of properties, AdCP supports **property tags** to make the system manageable:

* **Products** can reference `["local_radio", "midwest"]` instead of listing hundreds of stations
* **Buyers** use `get_adcp_capabilities` to discover the agent's portfolio and validate authorization
* **Authorization validation** works on the resolved properties via `adagents.json`

## Authorization Validation Workflow

Here's how a buyer agent validates that a sales agent is authorized to represent claimed properties:

### 1. One-Time Setup

`media_buy.portfolio.publisher_domains` is a **warm-up hint, not an allowlist**. It
is bounded (100 entries from 3.3) and a seller with a larger portfolio truncates it
and sets `publisher_domains_complete: false`. Prefetching it saves a round trip; it
never decides authorization. Each publisher's own `adagents.json` is the authority,
so resolve on demand for whatever the hint omits.

Every domain here comes from a counterparty, so each fetch is a counterparty-supplied
URL. Use the [SSRF profile](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#webhook-url-validation-ssrf)
— HTTPS only, address-family and private-IP filtering, no cross-origin redirects,
connect and total deadlines, and the 1 MiB publisher-origin body cap — not a bare
`fetch`. The domain pattern admits dotless labels like `localhost` or `metadata`, so
an unfiltered fetch is an internal-host oracle.

```javascript theme={null}
// Get portfolio information from capabilities
const capabilities = await salesAgent.call('get_adcp_capabilities');
const portfolio = capabilities.media_buy?.portfolio;
const publisherDomains = portfolio?.publisher_domains || [];

// Warm the cache for the domains the seller advertised. A key is absent
// until the domain has been resolved; a present key holds the parsed
// adagents.json. Absence means "not looked up", never "not authorized" —
// step 2 depends on that difference.
const authorizationCache = {};

// Bound how many publisher origins one seller can make you fetch, and how
// many at once. Both the product list and each product's publisher_properties
// are seller-sized, so without a cap the seller chooses your fan-out.
const MAX_RESOLVED_DOMAINS = 200;
const RESOLVE_CONCURRENCY = 6;
const NEGATIVE_TTL_MS = 60_000;

// A failed fetch is NOT a cached denial. Keep failures here, with a TTL, so a
// transient DNS blip or 503 cannot permanently deny a publisher that really
// did authorize this seller — that is the deny-on-miss bug in a new place.
const retryAfter = new Map();

async function loadAdagents(domain) {
  try {
    // ssrfSafeFetch applies the profile above. Do not substitute bare fetch():
    // its default redirect: "follow" will chase a publisher's 302 anywhere.
    const res = await ssrfSafeFetch(`https://${domain}/.well-known/adagents.json`, {
      connect: 5_000, total: 10_000, body: 1 << 20, redirect: 'error',
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`); // a 404 body is not a catalog
    return { ok: true, adagents: await res.json() };
  } catch (error) {
    // Do not log the domain verbatim into an admin UI; it is attacker-influenceable.
    console.warn('Could not reach a publisher origin to verify authorization');
    retryAfter.set(domain, Date.now() + NEGATIVE_TTL_MS);
    return { ok: false };
  }
}

/** Resolve a deduplicated domain set under a fixed fetch and concurrency cap. */
async function resolveDomains(domains) {
  const now = Date.now();
  const pending = [...new Set(domains)].filter(
    (d) => !(d in authorizationCache) && (retryAfter.get(d) ?? 0) <= now,
  );
  if (Object.keys(authorizationCache).length + pending.length > MAX_RESOLVED_DOMAINS) {
    // Refuse the overflow rather than fetching it. A seller that names more
    // publisher origins than this is not one you can validate in one pass.
    throw new Error('Publisher resolution budget exceeded');
  }
  for (let i = 0; i < pending.length; i += RESOLVE_CONCURRENCY) {
    const batch = pending.slice(i, i + RESOLVE_CONCURRENCY);
    const loaded = await Promise.all(batch.map(loadAdagents));
    batch.forEach((domain, j) => {
      // Only a successful fetch enters the cache. Failures stay retryable.
      if (loaded[j].ok) authorizationCache[domain] = loaded[j].adagents;
    });
  }
}

await resolveDomains(publisherDomains);
```

### 2. Product Validation

Authorize against the publisher named on the **product**, not against the
capabilities hint. A domain missing from the hint means "not prefetched", so look
it up. Only a fetched-and-checked `adagents.json` can authorize; a publisher whose
origin could not be reached is **unproven, not denied** — fail the buy for it, but
retry rather than caching the failure as a decision.

Resolve the whole product's domains in one bounded pass before checking any of
them. Fetching inside the per-property loop lets a seller pick your fan-out: both
`products[]` and each product's `publisher_properties[]` are seller-sized, so one
response with thousands of entries pointed at a third party turns your buyer into
a reflector.

```javascript theme={null}
// When evaluating a product
const result = await salesAgent.call('get_products', {
  idempotency_key: "550e8400-e29b-41d4-a716-446655442070",
  buying_mode: "brief",
  brief: "Chicago radio ads",
});
const product = result.products[0];

// One bounded, deduplicated resolve pass for everything this product names.
// Cache misses are expected whenever publisher_domains_complete is false;
// they mean "not prefetched", never "not authorized".
await resolveDomains(product.publisher_properties.map((p) => p.publisher_domain));

// Validate authorization for each publisher in publisher_properties
const authorized = product.publisher_properties.every(pubProp => {
  const adagents = authorizationCache[pubProp.publisher_domain];

  // Absent means unreachable or unparseable, so authorization is unproven.
  // Fail this buy — but the entry stays retryable, so a transient outage
  // does not become a permanent denial for a publisher that did authorize.
  if (!adagents) return false;

  // Verify the sales agent is in publisher's authorized_agents
  return adagents.authorized_agents.some(agent =>
    agent.url === salesAgent.url &&
    isAuthorizedForProperties(agent, pubProp)
  );
});

if (!authorized) {
  throw new Error("Sales agent not authorized for claimed properties");
}
```

### 3. Ongoing Validation

* **Cache adagents.json** responses with reasonable TTL (e.g., 24 hours)
* **Re-validate periodically** for long-running campaigns
* **Handle authorization changes** gracefully (pause vs. reject)

## Benefits of This Approach

### For Publishers

* **Explicit control** over who can sell their inventory
* **Granular permissions** by property, collection, country, and date range
* **Standard web hosting** - no special infrastructure required
* **Audit trail** of authorized agents

### For Sales Agents

* **Clear authorization proof** that buyers can verify
* **Efficient tag-based grouping** for large property portfolios
* **Standardized declaration** across all AdCP interactions

### For Buyer Agents

* **Automated verification** of seller authorization
* **Fraud prevention** through cryptographic verification
* **Confidence in purchases** from verified inventory sources
* **Scalable validation** for large-scale automated buying

## Security Considerations

### Domain Verification

* **HTTPS required**: adagents.json must be served over HTTPS
* **Domain ownership**: Only domain owners can authorize agents for their properties
* **Regular validation**: Buyers should re-check authorization periodically

### Authorization Scope

* **Least privilege**: Grant minimal necessary permissions
* **Time bounds**: Use start/end dates for temporary authorizations
* **Property restrictions**: Limit to specific paths or property types when appropriate

### Error Handling

* **Missing adagents.json**: Treat as unauthorized (fail closed)
* **Invalid JSON**: Reject malformed authorization files
* **Network errors**: Implement retry logic with fallback policies
* **Expired authorization**: Handle gracefully in active campaigns

## Integration with Product Discovery

Authorization validation integrates seamlessly with [Product Discovery](../../media-buy/product-discovery/):

1. **Discover products** using [`get_products`](../../media-buy/task-reference/get_products)
2. **Validate authorization** for properties referenced in products
3. **Proceed confidently** with authorized inventory
4. **Flag unauthorized** products for manual review

This creates a trustworthy foundation for AI-powered advertising that prevents unauthorized resale while enabling efficient, automated transactions.

## Technical Implementation

For complete technical details on implementing the `adagents.json` file format, including:

* File location and format requirements (`/.well-known/adagents.json`)
* JSON schema definitions and validation rules
* Mobile application and CTV implementation patterns
* Detailed property type specifications (website, mobile app, CTV, DOOH, podcast)
* Domain matching rules and wildcard patterns
* Validation code examples and error handling
* Security considerations and best practices

See the **[adagents.json Tech Spec](./adagents)** for complete implementation guidance.

## Related Documentation

* **[`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities)** - Discover agent capabilities and portfolio information
* **[Product Discovery](../../media-buy/product-discovery/)** - How authorization integrates with product discovery
* **[Properties Schema](https://adcontextprotocol.org/schemas/3.3.0-beta.0/core/property.json)** - Technical property data model
* **[adagents.json Tech Spec](./adagents)** - Complete `adagents.json` implementation guide


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.