> ## Documentation Index
> Fetch the complete documentation index at: https://docs.semicola.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 access and credentials.

## What a buyer agent is

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.

Semicola keeps these concepts separate:

* **Agent**: the software actor you recognize by name.
* **Principal**: the durable internal identity Semicola uses for routing and audit (`bap_…`).
  Most people only need it when troubleshooting.
* **Credential**: how the software proves its identity. Secrets are shown once and are never
  displayed again.
* **Grant**: explicit permission to act for an advertiser. A principal or a credential is not a
  grant.

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

The supported **Add agent** flows connect external software: software operated outside Semicola
that calls the v3 MCP endpoint with its own credential. Semicola-hosted agents are not available.

## Before you start

Only a person who is signed in directly as an administrator of the organization can read or
change any of this. An API key session and a buyer-agent credential are refused, including for
reads. The organization is always resolved from who you are signed in as; a request can never
reach another organization's principals.

```bash theme={null}
BASE=https://api.semicola.com/api/v2
```

These endpoints take your signed-in session; the console calls them for you.

## Identity and credentials

Semicola issues each principal an opaque `principalId` (for example `bap_01k…`). That is the only
identifier of the principal. It is a routing and audit handle: it is not a credential, and holding
it grants nothing.

Everything else attached to a principal is a **credential fact**: an API key ID and prefix, or an
M2M client ID. Credential facts identify a credential, never the principal. Rotating a credential
changes its facts and keeps the same `principalId`.

A principal has a **lifecycle state**: `active`, `suspended` or `retired`. A buyer agent is
suspended when it has no active credential left; every call it makes is then refused until you
add one. Its `principalId` is retained for audit and is never reissued.

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

## Manage buyer agents in the console

Open **Settings → API keys → Manage buyer agents**. This opens the management page at
`/<account-id>/buyer-agents`. It shows, for the organization you are signed in to, each agent's
name, credential status and advertiser access. Purpose and operator information are labeled
unavailable.

An agent's detail page has three tabs:

* **Overview**: the agent's name, lifecycle state, registered date, credential and advertiser
  access summary, and its current limitations.
* **Access**: choose exact advertisers and **Read** or **Read and write** for each one.
* **Settings**: credentials (rotate an API key, revoke a credential) and the agent's lifecycle
  (pause, resume, retire).

Pausing (suspending) an agent and retiring it require typing the agent's exact current display
name before the action is available. Resuming a paused agent does not. Retiring an agent is
terminal and cannot be reversed.

The page never shows a credential secret after it is created. A secret is delivered exactly once,
by the issue or rotate response itself, and cannot be retrieved afterwards: not by the console,
not by the API and not by support.

From the **Add agent** dialog you can:

* **Connect an external agent**: bind an account API key your organization already owns to a new
  buyer agent. Only a key that has never been used is accepted, and a key binds once, ever.
* **Create a custom agent**: Semicola creates a new M2M client and shows its secret once.

## Grant advertiser access

Open an agent's **Access** tab and add advertisers from your organization. Choose **Read** when
the agent should only inspect that advertiser, or **Read and write** when it may submit changes.
Saving reconciles the complete list; removing an advertiser revokes that agent's access to it. A
new advertiser is never granted automatically.

The console sends the revision it most recently read with every save. If another administrator
changes access first, the server returns a conflict. The console does not retry or merge that
request: reload the current grants, review them, then re-apply the intended changes.

### What a grant allows

* Grants name advertisers your organization owns. A request that names another customer's
  advertiser is refused.
* A buyer agent works under one advertiser at a time. It may pick one of its granted advertisers
  with the `X-Advertiser-Id` header; otherwise it works under its lowest **Read and write**
  advertiser, or its lowest **Read** advertiser when it has no writable grant. A call that names a
  record (a campaign, creative, media buy, catalog or collection) works under that record's
  advertiser.
* **Read** allows reads of that advertiser's campaigns, creatives, media buys and reporting. Any
  write with a **Read** grant is refused. **Read and write** allows writes as well.
* A record addressed by id must belong to a granted advertiser; otherwise it is not found. One
  call cannot touch two advertisers.
* A buyer agent cannot switch accounts, open pages, change seller connections or create
  advertisers.
* Buyer agents act on the v3 MCP endpoint (`/mcp/v3`) only. REST, the v2 MCP servers and seller
  agents refuse their credentials.

## Read your organization's principals

```bash theme={null}
curl "$BASE/buyer-agent-principals"
```

The response has two collections. `operator` is your organization acting as itself; it carries no
credentials. `buyerAgents` lists each buyer agent with its `principalId`, `principalKind`,
`displayName`, `lifecycleState`, `createdAt`, `access` and `credentials`. `access` contains
`revision` and the current advertisers, each with an `advertiserId`, `advertiserName` and a
`READ` or `READ_WRITE` role.

Each credential carries `credentialType` (`api_key` or `m2m`), `binding` (`active` or `retired`),
the non-secret selector the rotate and revoke calls accept (`serviceTokenId` for an API key,
`subject` for an M2M client), `lastUsedAt`, `createdAt` and `retiredAt`. The read returns no
secret.

## Set advertiser access

The API replaces the full exact set in one request. First read the current `access.revision`,
then send it as `expectedAccessRevision`:

```bash theme={null}
curl -X PUT "$BASE/buyer-agent-principals/<principal-id>/access" \
  -H 'Content-Type: application/json' \
  --data '{
    "expectedAccessRevision": 4,
    "advertisers": [
      {"advertiserId": 42, "role": "READ"},
      {"advertiserId": 43, "role": "READ_WRITE"}
    ]
  }'
```

An HTTP `409` means the revision is stale. Fetch the principal list again and re-apply the
intended list; never retry the same body automatically. Removing an advertiser revokes its active
grant; changing a role records a revocation and a new grant for the new role. Every call re-reads
the active grants, so a change takes effect on the agent's next call.

## Register an API key

```bash theme={null}
curl -X POST "$BASE/buyer-agent-principals" \
  -H 'Content-Type: application/json' \
  -d '{"displayName":"Planning agent","credential":{"type":"api_key","serviceTokenId":"<key-id>"}}'
```

Create the key under **Settings → API keys** first and do not use it before registering it. The
agent then calls `/mcp/v3` with `Authorization: Bearer <key>`.

## Issue an M2M credential

Send a new idempotency key for this issuance:

```bash theme={null}
curl -X POST "$BASE/buyer-agent-principals/issue" \
  -H 'Content-Type: application/json' \
  -d '{"displayName":"Reporting automation","idempotencyKey":"<uuid>"}'
```

Save `credential.clientSecret` immediately in your secret manager. It is shown once and cannot be
retrieved or regenerated; a retry with the same idempotency key returns the principal with
`clientSecret: null`. The response also names the `tokenEndpoint` and the grant type.

The agent exchanges its client ID and secret for a short-lived access token with the OAuth
`client_credentials` grant (`client_secret_basic` or `client_secret_post`), scope `mcp:access` and
resource `https://api.semicola.com/mcp/v3`:

```bash theme={null}
curl -X POST https://api.semicola.com/auth/token \
  -u '<client-id>:<client-secret>' \
  -d grant_type=client_credentials \
  -d scope=mcp:access \
  -d resource=https://api.semicola.com/mcp/v3
```

Tokens last 15 minutes and come without a refresh token; request a new one when it expires. No
token is issued while the agent has no advertiser grant.

To replace an issued M2M client while keeping the buyer agent's identity, issue the successor
with `replacesM2MSubject` set to the current client ID. The old client stops working.

## Rotate an API key

Rotate a registered API key with only its current key ID. Semicola creates the successor, binds
it to the same buyer agent and revokes the old key in one step:

```bash theme={null}
curl -X POST "$BASE/buyer-agent-principals/rotate" \
  -H 'Content-Type: application/json' \
  -d '{"type":"api_key","currentServiceTokenId":"<key-id>"}'
```

The response includes the new key's secret once. Save it before deploying the successor.

## Revoke a credential

To permanently stop a credential from authenticating, use `POST /buyer-agent-principals/revoke`.
Revocation is immediate and cannot be undone by registering the same credential again.

```bash theme={null}
curl -X POST "$BASE/buyer-agent-principals/revoke" \
  -H 'Content-Type: application/json' \
  -d '{"type":"m2m","subject":"<client-id>"}'
```

For an API key, use `{ "type": "api_key", "serviceTokenId": "<key-id>" }`. If this was the buyer
agent's last active credential, the principal becomes `suspended` and every call it makes is
refused until you issue or register another credential for it.

## Pause, resume or retire an agent

Send a lifecycle control to `POST /buyer-agent-principals/<principal-id>/lifecycle-controls`
with the lifecycle state you reviewed, so a stale screen cannot silently change newer state:

* `{"kind":"suspend","expectedLifecycleState":"active","confirmationText":"<display name>"}`
* `{"kind":"resume","expectedLifecycleState":"suspended"}`
* `{"kind":"retire","expectedLifecycleState":"active","confirmationText":"<display name>"}`
  (or `"suspended"`). Retiring revokes every credential and cannot be reversed.

## Not available yet

The original also offers agent Connections, Notifications and Activity tabs, renaming an agent,
Semicola-hosted agents, Slack channel bindings, seller-account administration reads and
destination or subscriber controls, and OAuth-client registration. Semicola does not serve these
yet.
