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

# A buyer agent onboards itself

> Follow a buyer agent from publishing its keys and trust record to a live account with a seller, with no seller-issued credential and no capabilities endpoint of its own.

This walkthrough follows one buyer agent from nothing to a working account with a seller it has never spoken to. The agent publishes its identity, reads the seller's onboarding posture, makes first contact with a signed request, and provisions an account. It never runs a capabilities endpoint of its own and never receives a seller-issued credential: the signature is the credential.

> [!NOTE]
>
> **Experimental, AdCP 3.3.** This flow uses several experimental surfaces:
>
> * the Web Bot Auth signing profile ([Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities));
> * trust.json ([trust.json](/dist/docs/3.3.0-beta.0/reference/trust-json));
> * demand-side grants ([Grants in trust records](/dist/docs/3.3.0-beta.0/building/by-layer/L2/accounts-and-agents#grants-in-trust-records-experimental));
> * seller onboarding posture ([`account.agent_onboarding`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities#accountagent_onboarding));
> * relationship status on [`get_principal`](/dist/docs/3.3.0-beta.0/protocol/get_principal#relationship-status).
>
> They may change between 3.x releases with at least 6 weeks' notice. SDK support is in progress, so the examples show the wire, not SDK calls. Design and decisions: [#8118](https://github.com/adcontextprotocol/adcp/issues/8118), DR-0023, DR-0025.

## Scenario

Sam runs buying at Pinnacle Agency. Pinnacle's buyer agent buys media for Nova Motors and other advertisers. Priya runs ad products at StreamHaus, a streaming publisher whose sales agent Pinnacle has never called. Sam wants the agent to open an operator-billed account with StreamHaus for Nova Motors, today, without a partnerships email thread.

| | Pinnacle (buyer) | StreamHaus (seller) |
| - | - | - |
| Agent origin | `https://buyer.pinnacle-agency.example` | `https://ads.streamhaus.example` |
| Registrable domain | `pinnacle-agency.example` | `streamhaus.example` |
| Signing profile | `web-bot-auth` | verifies `web-bot-auth` and `adcp/request-signing/v1` |

## What `open` onboarding delivers

Self-onboarding automates becoming known. Under an `open` mode, a buyer agent gets a verified identity, sandbox access, and the ability to join a billing relationship that already exists, such as its operator's account with the seller. It does not let an unknown agent start spending: agent-billed accounts and new commercial relationships still go through the seller's application, with a human on each side.

## 0. Publish your identity

The agent's identity is its **origin**, one agent per origin. Three files make it verifiable.

**The key directory**, at the agent's origin, holds its signing keys. It is a JWK Set; each `kid` is the key's RFC 7638 thumbprint, and keys carry no AdCP-specific members:

```http theme={null}
GET /.well-known/http-message-signatures-directory HTTP/1.1
Host: buyer.pinnacle-agency.example

HTTP/1.1 200 OK
Content-Type: application/http-message-signatures-directory+json
Content-Digest: sha-256=:...:
Signature-Input: key1=("@authority";req "content-digest");created=1791900000;expires=1791986400;keyid="NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs";tag="http-message-signatures-directory"
Signature: key1=:...:

{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "...", "kid": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" } ] }
```

The response is signed with each key and carries an `expires`, so publishing it is a scheduled job (regenerate it before it expires), not a one-time upload.

**The trust record**, at the registrable domain, lists the origin and its role. It carries no keys:

```json theme={null}
{
  "agents": [
    { "url": "https://buyer.pinnacle-agency.example/mcp", "roles": ["adcp:buying"] }
  ]
}
```

served at `https://pinnacle-agency.example/.well-known/trust.json`. Sellers derive this location from the agent's origin, so nothing points to it.

**The revocation list**, at the agent's origin, is where the agent revokes a key. It is served at `/.well-known/governance-revocations.json` as a JWS of type `adcp-gov-revocation+jws`, signed with a key from the directory, even while it revokes nothing. Its payload:

```json theme={null}
{
  "version": 1,
  "issuer": "https://buyer.pinnacle-agency.example",
  "updated": "2026-10-13T14:55:00Z",
  "next_update": "2026-10-13T15:10:00Z",
  "revoked_kids": []
}
```

The list is re-signed before its `next_update`, so publishing it is a scheduled job too. The format and polling rules are in [Transport revocation](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#transport-revocation). Without it, a seller can verify you but cannot bind you for spend.

**Grants from the advertisers.** Nova Motors grants Pinnacle authority to buy for it, in Nova Motors' own trust.json:

```json theme={null}
{
  "grants": [
    {
      "grantee": "pinnacle-agency.example",
      "scopes": ["adcp:media_buying"],
      "agents": [{ "url": "https://buyer.pinnacle-agency.example/mcp" }],
      "profiles": { "adcp": { "brands": ["nova_motors"] } }
    }
  ]
}
```

A grant is published by the party making it, so the seller can verify it without trusting Pinnacle's word.

## 1. Find sellers

The agent finds StreamHaus through the AgenticAdvertising.org registry or the publisher's `adagents.json`, as today.

## 2. Read the seller's onboarding posture

An unsigned `get_adcp_capabilities` returns StreamHaus's posture. It is the same for every caller:

```json theme={null}
{
  "experimental_features": ["account.agent_onboarding"],
  "account": {
    "supported_billing": ["operator", "agent"],
    "sandbox": true,
    "agent_onboarding": {
      "sandbox": { "mode": "open" },
      "billing": {
        "operator": { "mode": "open", "requirements": [{ "kind": "grant" }] },
        "agent": {
          "mode": "application",
          "url": "https://streamhaus.example/partners/agent-billing",
          "requirements": [{ "kind": "billing_entity" }, { "kind": "contract" }]
        }
      },
      "url": "https://streamhaus.example/partners/buyer-agents",
      "message": "Sandbox is open to any verified buying agent. Operator-billed accounts are open when your operator already bills with us and the advertiser has granted your operator."
    }
  }
}
```

## 3. Check yourself against the requirements

The agent compares each requirement with what it knows about itself:

* **Operator-billed (`open`, requires `grant`):** Nova Motors' trust.json grants Pinnacle `adcp:media_buying`, and Pinnacle already bills with StreamHaus. Usable now.
* **Agent-billed (`application`):** needs a billing entity and a contract. That goes to Sam, with the URL.
* **Unknown requirement kinds** would fall back to `message` and `url`.

A seller marked `invite_only` would be dropped from the plan with the reason recorded.

## 4. First contact

The agent signs a `get_principal` request with the [Web Bot Auth profile](/dist/docs/3.3.0-beta.0/building/by-layer/L1/wba-profile). The covered `Signature-Agent` member names its origin:

```http theme={null}
POST /mcp HTTP/1.1
Host: ads.streamhaus.example
Content-Type: application/json
Content-Digest: sha-256=:...:
Signature-Agent: sig1="https://buyer.pinnacle-agency.example"
Signature-Input: sig1=("@method" "@target-uri" "@authority" "content-type" "content-digest" "signature-agent";key="sig1");created=1791903600;expires=1791903900;nonce="feeG8NGBMs3K7BOSl6_Fw3kb1-bupF1WT-1iCRLNPaqfALoUmG8A0ZqhHWEck3K65l1JLYdv0BFklWhPKO0c-Q";keyid="NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs";alg="ed25519";tag="web-bot-auth"
Signature: sig1=:...:
```

StreamHaus fetches the directory at the named origin, finds the key by thumbprint, verifies the signature, and checks the origin's revocation list. Pinnacle is now **identity-established**: StreamHaus knows exactly which origin is calling, but has not bound it to anything. `get_principal` returns:

```json theme={null}
{ "status": "completed", "result": { "kind": "unconfigured" } }
```

## 5. Configure the principal

The agent calls `sync_principal` with its declarations and webhooks. Because StreamHaus's posture has an `open` mode, StreamHaus:

1. derives Pinnacle's trust record from the origin (`https://pinnacle-agency.example/.well-known/trust.json`);
2. confirms it lists `https://buyer.pinnacle-agency.example` with role `adcp:buying`;
3. creates a principal limited to the open modes.

A seller without an `open` mode would hold this for its own approval. In that case `get_principal` shows `relationship.status: "pending_approval"`, with any applicant-specific `setup` step.

## 6. Open the account

The agent calls `sync_accounts` for Nova Motors, operator-billed:

```json theme={null}
{
  "accounts": [
    {
      "brand": { "domain": "novamotors.example", "brand_id": "nova_motors" },
      "operator": "pinnacle-agency.example",
      "billing": "operator"
    }
  ]
}
```

StreamHaus checks authority from Nova Motors' trust.json. The grant names Pinnacle, covers `adcp:media_buying`, and `account.operator` is Pinnacle, the signer's operator. Pinnacle already has an operator billing relationship with StreamHaus, so the account comes back `active`. Without the grant, the request would fail with `PERMISSION_DENIED` and `reason: "grant_absent"`. No account is created, and Sam knows to ask Nova Motors for the grant.

## 7. Stay current

The agent subscribes to `principal.changed`. When StreamHaus approves agent billing, suspends the relationship, or the relationship nears its `expires_at`, the webhook fires (`relationship_changed`, `relationship_expiring`), and the agent re-reads `get_principal`. A lapse arrives as a warning, not as `AGENT_SUSPENDED` in the middle of a campaign.

## Failure branches

| What happens | What the agent sees | What to do |
| - | - | - |
| The seller is `invite_only` for every mode | The posture | Drop the seller and record why. |
| The mode is `application` | The posture's `url` and requirements | Hand the URL and unmet list to a human. Watch `get_principal` for `relationship`. |
| The trust record doesn't list the origin | `PERMISSION_DENIED` with no `details` | Add the origin to `trust.json` with role `adcp:buying`. |
| No grant covers the brand | `PERMISSION_DENIED`, `reason: "grant_absent"` | Ask the advertiser, or the agency acting for it, to publish the grant. |
| The origin hosts two agents | `PERMISSION_DENIED` with no `details`, because the trust record fails the match | Give each agent its own subdomain. |
| Pinnacle revokes a key with `reason: keyCompromise` | Requests under that key get `AUTH_INVALID`; buys stand | Review the buys the seller shows you; pause or cancel the ones you didn't intend with `control_media_buy`. |

## If you sign with the RFC 9421 profile today

The RFC 9421 profile (`adcp/request-signing/v1`, keys through brand.json) is deprecated in 3.3 and works through 3.x. A buyer moving to Web Bot Auth publishes the directory, trust record, and revocation list above and keeps its brand.json keys until the sellers it uses have moved. A seller binds the new origin onto an existing principal only with explicit approval, so arrange that with each seller. See the [agent identity migration guide](/dist/docs/3.3.0-beta.0/reference/migration/agent-identity-3-3).

## Related reference

* [Web Bot Auth identities](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#web-bot-auth-identities)
* [First contact and binding](/dist/docs/3.3.0-beta.0/building/by-layer/L2/accounts-and-agents#first-contact-and-binding)
* [Grants in trust records](/dist/docs/3.3.0-beta.0/building/by-layer/L2/accounts-and-agents#grants-in-trust-records-experimental)
* [`account.agent_onboarding`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities#accountagent_onboarding)
* [Relationship status](/dist/docs/3.3.0-beta.0/protocol/get_principal#relationship-status)
* [trust.json](/dist/docs/3.3.0-beta.0/reference/trust-json)
* [Set up your buyer agent with a seller](/dist/docs/3.3.0-beta.0/accounts/principal-registration-walkthrough): the principal layer in depth


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