> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage buyer agents

> Add connected software agents to your buyer organization and manage their advertiser access, lifecycle and credentials with save_buyer_agent and the Apostra web app.

A **buyer agent** is a connected software actor installed in your buyer organization.
It is not a person, and no person is assigned to or represented by an agent. Agents sit
beside advertisers in the buyer workspace.

Apostra keeps six concepts separate:

* **Agent**: the software actor you recognize by name and purpose.
* **Installation**: that agent's presence in your organization.
* **Principal**: the durable internal identity Apostra uses for routing and audit. Its
  `principalId` (for example `prin_yq8Kd...`) is the only identifier of the principal.
  Most people need it only when troubleshooting.
* **Credential**: how the software proves its identity. Secrets are shown once and never
  displayed again.
* **Connection**: a configured way for the agent to connect to Apostra, exchange data,
  or use an organization-owned reporting resource.
* **Grant**: explicit permission to act for an advertiser. An installation, principal,
  credential or connection is not a grant.

Adding an agent grants no advertiser, account, spend, payment, Terms or other business
authority. Until an administrator grants an advertiser, the agent shows **No advertisers
granted yet**.

In a Developer account, each buyer agent also has an agent record of the `buying` kind,
so the account's status lists it with its stage, as it lists a sales or creative agent.
The record only tracks the agent. The principal stays the credential, and the agent's
authority stays as advertiser grants on the Buyer account.

An agent runs in one of two ways under the same model. An **external agent** is software
you run yourself and connect through the flows below. A **hosted agent** is software
Apostra runs for you; see [Hosted buyer agents](https://docs.apostra.com/v3/hosted-buyer-agents.md).

## Who can manage buyer agents

Only a person signed in directly as an administrator of the organization can list,
read or change the organization's buyer agents from a Buyer or Developer account. A
support session, an impersonated or simulated session, a delegated session, an API key
and an OAuth client are all refused, reads included, with `ACCESS_DENIED`. The one
exception is a buyer agent reading its own record with `id: "self"`. The organization
always comes from who you are signed in as. A call cannot name a different organization,
so it can never reach another organization's principals.

Identity is not authority. Holding a principal or a credential gives no advertiser,
account, spend, payment or Terms access. That comes from separate per-advertiser grants
and is never implied by identity.

A principal has a **kind** and a **lifecycle state**:

* A **buyer agent** is one automation your organization runs. It holds credentials, and
  you administer those credentials.
* An **organization operator** is your organization acting as itself. Apostra creates it
  the first time an organization credential calls. It is not a buyer agent and has no
  credentials to administer.
* The lifecycle state is `active`, `suspended` or `retired`. A buyer agent is suspended
  when it has no active credential left, and every call it makes is then refused until
  you add one. Its `principalId` is kept for audit and never reissued.

## Read your agents

Use `search` with `kind: "buyer_agent"` to list the organization's buyer agents. It takes
a `query` and a `limit`, and returns `total` and whether the list was `truncated`. Use
`get` with `kind: "buyer_agent"` and the agent's `principalId` as `id` for one agent. An
agent can read itself with `id: "self"`.

Each agent has these fields:

| Field | Meaning |
| - | - |
| `principalId`, `displayName`, `registeredAt` | Who the agent is and when it was added. |
| `lifecycleState` | `active`, `suspended` or `retired`. |
| `accountId`, `hosting` | For a hosted agent, the account it belongs to and how it runs. See [Hosted buyer agents](https://docs.apostra.com/v3/hosted-buyer-agents.md). |
| `credentials`, `credentialCount`, `activeCredentialCount` | Each credential's `credentialType`, `bindingState` (`active` or `retired`), `boundAt`, `lastVerifiedAt`, `retiredAt` and an `administrationHandle`, the one non-secret selector that identifies it. No secret is ever returned. |
| `access` | `accessRevision` and the granted advertisers, each with `advertiserId`, `name` and a `READ` or `READ_WRITE` `role`. |
| `notifications` | For an administrator, the agent's notification subscribers and reporting destinations for each storefront, with a `proof_state` of `pending`, `verified`, `failed` or `stale`. |
| `credentialHandoff` | Present only on the response to the call that prepared a handoff, and on a read by the administrator who prepared it. |

A read returns no issuer URLs, WorkOS organization IDs, client IDs, domains,
`brand.json` or JWKS URLs, signing key IDs or key fingerprints. Those are credential
facts, not identity.

## Create, rename and change an agent

`save_buyer_agent` states the end state you want, and the fields you send decide the
change. With no `id` it creates an agent. With an `id` it makes exactly one change:

| Send | Change |
| - | - |
| `displayName` | Rename the agent. |
| `access` | Replace its advertiser grants. |
| `lifecycleState` with `expectedLifecycleState` | Pause, resume or retire it. |
| `credentialHandoff: true` | Prepare credential setup. |

A call that sends fields from more than one change, or an `id` with none, is rejected
with `VALIDATION_ERROR`.

To create an external agent, send `displayName`:

```json theme={null}
{ "displayName": "Planning agent", "kind": "external" }
```

Creation stands alone. Use the returned `id` in a later call to set access or lifecycle.
Sending `idempotencyKey` with an external create is refused; it is only for
`kind: "hosted"`.

### Set advertiser access

`access` is a complete replacement of the agent's grants, up to 500 advertisers, and it
must carry the `expectedAccessRevision` from your latest read. A stale revision is a
conflict. Read the agent again and re-apply the list you want, and never retry the same
body automatically.

```json theme={null}
{
  "id": "<principal-id>",
  "access": {
    "expectedAccessRevision": 4,
    "advertisers": [
      { "advertiserId": "42", "role": "READ_WRITE" },
      { "advertiserId": "43", "role": "READ" }
    ]
  }
}
```

Removing an advertiser revokes that grant. Changing a role records a revocation and a new
grant, and every advertiser operation checks the active grant again. A new advertiser is
never granted automatically. An account's own hosted agent cannot be given access this
way, because its access is the account's advertisers; see
[Hosted buyer agents](https://docs.apostra.com/v3/hosted-buyer-agents.md#what-you-cannot-change-on-an-accounts-own-agent).

### Pause, resume or retire

Lifecycle changes are state-fenced. Send `lifecycleState` with `expectedLifecycleState`,
the state the agent must be in for the change to apply.

| Change | `lifecycleState` | `expectedLifecycleState` | `confirmationText` |
| - | - | - | - |
| Pause | `suspended` | `active` | The agent's exact current display name |
| Resume | `active` | `suspended` | Not needed |
| Retire (permanent) | `retired` | `active` or `suspended` | The agent's exact current display name |

```json theme={null}
{
  "id": "<principal-id>",
  "lifecycleState": "suspended",
  "expectedLifecycleState": "active",
  "confirmationText": "Planning agent"
}
```

If the agent is already in the state you send, the call succeeds and changes nothing, so
a retry after a lost reply is safe. A retired agent cannot be resumed.

## What a grant allows

* **A grant names advertisers your organization owns**, in your account or in another
  account of your organization. A buyer agent never reaches more than your organization's
  own users can. A grant on an advertiser another organization owns is refused with
  `ACCESS_DENIED` and `details.reason: "ADVERTISER_OUTSIDE_ORGANIZATION"`, even when that
  organization shared the advertiser with yours through an
  [advertiser grant](https://docs.apostra.com/v3/advertiser-grants.md). A grant of that kind saved earlier gives the
  agent nothing: the advertiser is left out of its access list and every request for it
  or its campaigns is refused. An archived advertiser is left out the same way.
* **`READ`** lets the agent read an advertiser's campaigns, creatives, media buys and
  reporting. On V3 the read tools are `get`, `search`, `get_status`, `get_delivery` and
  the `open_*` tools. Every other tool, including each `save_*` and `request_*` tool, is a
  write. A write under a `READ` grant is refused with `agent_read_only`. **`READ_WRITE`**
  allows writes as well.
* **A call names its advertiser.** On V3 it names it with `advertiserId`,
  `filter.advertiserId` on `search`, or `filters.advertiserId` on `get_delivery`, and
  `get` of an advertiser names it by `id`. A call that names none but addresses an
  existing campaign or media buy by id runs for the advertiser that owns it, when that
  campaign or media buy belongs to your organization. A `search` of an advertiser-owned
  kind such as catalogs or creatives that names no advertiser uses the agent's only
  granted advertiser, or lists its granted advertisers to choose from. Otherwise a call
  uses the connection's selected advertiser. An agent with grants on more than one
  advertiser has none selected, so such a call is refused with `agent_advertiser_required`,
  which lists up to 10 granted advertisers by id and name and says how many more there are.
  An agent with no active grant is refused with `agent_no_advertiser_access`, and so is a
  call that names an advertiser it has no grant on. Nothing is remembered between calls, so
  one session can work several granted advertisers.
* **The tools offered follow the grants**, not the kind of account the credential belongs
  to. When the credential belongs to your organization's parent account and the agent holds
  at least one grant, the V3 endpoint still lists its buyer tools, `get_status` reports a
  `buyer` account, and each call runs in the account below the parent that owns the
  advertiser it names. A call that names no advertiser and would act on the parent account
  itself, such as creating an advertiser or changing billing details, is refused, because no
  grant covers the parent. The agent can list its granted advertisers without naming one,
  with `search` of advertisers. An agent with no active grant keeps the parent account's
  organization tools.
* **A buyer agent cannot switch accounts.** `switch_account` stays refused for it.
* **Notification delivery re-checks the grant on every attempt**, so revoking a grant also
  stops that advertiser's notifications reaching the agent.
* **A grant can show fees.** If your organization charges clients an operator fee, a buyer
  agent sees only the total cost of a buy that carries it, unless the grant for that
  advertiser is marked `showsFeeBreakdown`. `save_buyer_agent` does not set that mark and
  keeps one set elsewhere when it replaces an agent's access. See
  [Operator fees](https://docs.apostra.com/v3/operator-fees.md).

## Credentials

A credential is how the agent's software proves who it is. Credentials are managed in the
Apostra web app, so the secret never passes through chat: open **Settings → API keys →
Manage buyer agents**. The page shows, for the organization you are signed in to, each
agent's name, connection status, advertiser access and recent activity, and an agent
detail page with **Overview**, **Access**, **Connections**, **Notifications**,
**Activity** and **Settings**. The page never shows a credential secret. A secret is
delivered once, by the response that issues or rotates it, and cannot be retrieved
afterwards, not by the console, not by the API and not by support.

From the agent's page an administrator can:

* **Issue a credential.** Apostra creates a new identity-only OAuth client and shows its
  secret once.
* **Register an existing credential.** Bind an API key or OAuth client your organization
  already owns to a new buyer agent. Registering does not change what that credential could
  already do.
* **Rotate an API key.** Apostra creates the replacement, shows its secret once, and retires
  the current key.
* **Revoke a credential.** It stops authenticating immediately and cannot be registered
  again.

Rotating and revoking both retire a live credential, so both need you to retype the agent's
name. Revoking an agent's last active credential suspends the agent.

An issued credential is identity only. It can authenticate, but it has no action
permissions, and business calls (advertiser, account, spend, payment, Terms and tool
operations) are refused until an administrator grants advertiser access. Save a
secret in your secret manager as soon as it is shown. If issuing a credential reports
`ISSUANCE_RECONCILIATION_REQUIRED`, repeat the same request once the transient failure has
cleared, and do not start another issuance until that retry has settled. If it still
reports that reconciliation is required, keep the request ID and contact support.

The **Access** tab lets an administrator select exact advertisers and choose **Read** or
**Read and write** for each. **Settings** lets an administrator rename the agent and pause,
resume or retire it, with the same name confirmation as above. Deactivating a single
notification destination or subscriber is also done there, and needs the same retyped name.

### Hand off credential setup after building an agent

A coding agent can prepare credential setup for an existing, active external agent that has
never had a credential. A directly signed-in organization administrator sends
`save_buyer_agent` with the agent's `id` and `credentialHandoff: true`. The response carries
`credentialHandoff`: a non-secret `reference`, the agent's `principalId`, an `expiresAt`
time, a `verificationState`, and the `ceremonyPath` for completing setup. Preparing setup
creates neither a credential nor advertiser access, and the coding agent's own credentials
stay separate from the runtime's.

The administrator who prepared the handoff opens `ceremonyPath`. It opens **Settings → API
keys → Manage buyer agents** for that agent. The administrator submits the credential form,
receives the secret once, and stores it in the runtime's protected secret store. Never put
the secret in chat, a coding-agent prompt, source code, logs or Activity. The coding agent
receives only the reference and the verified setup state.

The reference expires after 15 minutes if unused, and the same administrator can prepare
another. It works only for the administrator and organization that prepared it, and for the
agent it names. Read `get({ "kind": "buyer_agent", "id": "<principal-id>" })` as that
administrator to check `credentialHandoff.verificationState`:

| State | Meaning |
| - | - |
| `pending` | The administrator has not completed credential setup. |
| `issuing` | Apostra is completing credential creation. |
| `credential_active` | Apostra created a credential bound to this agent and it is still active. This does **not** confirm the runtime stored the secret, authenticated, or has advertiser access. |
| `expired` | The unused handoff expired. |
| `invalidated` | The agent or the handoff's credential is no longer eligible or active, including after rotation or revocation. |
| `reconciliation_required` | Issuance needs reconciliation. Do not assume a credential is ready or start another issuance. |

Only the administrator who started the handoff sees its reference, and not in search
results or to the runtime. Rotation and revocation stay the way to manage a delivered
credential. Advertiser access still needs its own grants.

## What must be ready

Readiness has five independent gates: registration, an active credential, current
configuration with recorded proof, the exact advertised capability, and separate business
authority. All five must be ready. A principal ID, credential, domain, destination or proof
result never satisfies the business-authority gate. These gates do not substitute for one
another. Single sign-on authenticates the person who installs or administers the
integration, a workload credential identifies the buyer agent, and only an explicit
agent-and-advertiser grant authorizes business operations. Spend, payment and Terms
authority stay separate. Apostra reports these boundaries and cannot create or approximate
a missing grant.

## Related

* [Hosted buyer agents](https://docs.apostra.com/v3/hosted-buyer-agents.md) covers hosted orchestrators and an
  account's own agent.
* [Run Apostra for several client accounts](https://docs.apostra.com/v3/several-client-accounts.md) compares buyer
  agents with other ways to separate clients.
* [Sign in a self-built agent](https://docs.apostra.com/v3/agent-sign-in.md) covers how an agent obtains its own
  access.
* [Build a reporting pipeline](https://docs.apostra.com/v3/reporting-pipeline.md) covers the machine notifications an
  agent subscribes to.


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