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

# Management Dashboard

> Expose your agent's management surface in the Placet dashboard by registering management credentials.

Placet's **Agent Management Dashboard** is an optional UI that lets a user inspect and control a connected agent runtime (sessions, audit logs, token usage, credentials, cron, MCP bridges, workspace, skills, scripts, channels, commands, agent card, A2A peers) directly from Placet — without ever exposing the agent's bearer token to the browser.

The protocol Placet speaks upstream is a bearer-authenticated **management API** rooted at `/api/v1/*`. Any agent runtime that implements the same surface — or a compatible subset — can be connected; the routes documented below describe the wire contract Placet expects upstream.

The dashboard is **off by default** per user. It becomes visible once a user enables it under **Settings → Preferences → Management Dashboard**. Placet auto-enables this preference the first time a user registers an agent that provides management credentials.

```mermaid theme={null}
sequenceDiagram
    participant Browser
    participant Placet
    participant Agent

    Browser->>Placet: GET /api/agents/:id/manage/sessions (JWT)
    Placet->>Placet: Look up stored management_url + api_key
    Placet->>Agent: GET {management_url}/api/v1/sessions<br/>Authorization: Bearer <stored key>
    Agent-->>Placet: 200 OK (JSON)
    Placet-->>Browser: 200 OK (JSON)
```

Placet owns a dedicated, typed endpoint for every upstream management route — it is **not** a generic wildcard proxy. Controllers are explicit to give the backend room for caching, cross-agent aggregation, and request shaping. When the upstream is unreachable Placet returns `502 Bad Gateway` and the frontend silently hides the view.

***

## Registering Management Credentials

An agent registers management credentials the same way it registers a channel — by calling `POST /api/v1/agents/setWebhook` on the Placet backend with the additional `management` object, or by using the dedicated `setManagement` endpoint.

### Option A — Combined with `setWebhook`

```http theme={null}
POST /api/v1/agents/setWebhook
x-api-key: <placet-agent-key>
Content-Type: application/json
```

```json theme={null}
{
  "channelId": "my-agent-id",
  "webhookUrl": "https://agent.example.com/placet/webhook",
  "management": {
    "url": "https://agent.example.com",
    "apiKey": "management-bearer-token"
  },
  "isSubagent": false,
  "parentChannelId": null
}
```

| Field               | Type             | Description                                                                                |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
| `management.url`    | `string` (URL)   | Base URL of the agent runtime (`/api/v1` is appended automatically).                       |
| `management.apiKey` | `string`         | Management bearer token. Stored encrypted, returned as `***` to the UI.                    |
| `isSubagent`        | `boolean`        | `true` marks this channel as a HITL sub-channel that should be hidden from the agent list. |
| `parentChannelId`   | `string \| null` | When `isSubagent` is `true`, the `channelId` of the parent agent owning this HITL channel. |

### Option B — Dedicated endpoint

```http theme={null}
POST /api/v1/agents/setManagement
x-api-key: <placet-agent-key>
Content-Type: application/json
```

```json theme={null}
{
  "channelId": "my-agent-id",
  "url": "https://agent.example.com",
  "apiKey": "management-bearer-token"
}
```

Pass `"url": null, "apiKey": null` to **clear** the management credentials for a channel.

***

## Endpoints

Every endpoint listed below lives under `/api/agents/:agentId/manage/` on the Placet backend and maps 1:1 to the upstream `/api/v1/` route. All requests require a valid Placet user JWT (`Authorization: Bearer <jwt>`) and are scoped to the authenticated user's agent.

### Health

| Method | Placet path | Upstream route | Description               |
| ------ | ----------- | -------------- | ------------------------- |
| GET    | `/health`   | `/health`      | Management liveness check |

### Sessions

| Method | Placet path       | Upstream route    | Description                     |
| ------ | ----------------- | ----------------- | ------------------------------- |
| GET    | `/sessions`       | `/sessions`       | List sessions (metadata only)   |
| GET    | `/sessions/{key}` | `/sessions/{key}` | Full history for a session key  |
| DELETE | `/sessions/{key}` | `/sessions/{key}` | Remove a session (cache + file) |

### Audit log

| Method | Placet path           | Upstream route        | Description                 |
| ------ | --------------------- | --------------------- | --------------------------- |
| GET    | `/audit`              | `/audit`              | Filtered event list         |
| GET    | `/audit/tail`         | `/audit/tail`         | Last N events from today    |
| GET    | `/audit/stats`        | `/audit/stats`        | Aggregated event counts     |
| GET    | `/audit/runs/{runId}` | `/audit/runs/{runId}` | All events for a single run |

### Token usage

| Method | Placet path           | Upstream route        | Description                       |
| ------ | --------------------- | --------------------- | --------------------------------- |
| GET    | `/usage`              | `/usage`              | Aggregated totals + grouped items |
| GET    | `/usage/runs/{runId}` | `/usage/runs/{runId}` | Single-turn token-usage row       |

### Settings

Runtime settings exposed by the agent. Browser settings include the new browser tool controls shown in the Management Dashboard: `enable`, `max_named_sessions`, `domain_allowlist`, and `domain_denylist`.

| Method | Placet path | Upstream route | Description                                      |
| ------ | ----------- | -------------- | ------------------------------------------------ |
| GET    | `/settings` | `/settings`    | Read editable runtime settings and option lists  |
| PATCH  | `/settings` | `/settings`    | Apply partial settings updates and restart hints |

### Credentials

Generic secrets stored on the agent and made available to skills/tools as `${credentials.KEY}`. The `exposed` flag mirrors `config.tools.exec.exposed_credentials` — when `true` the key is injected as an env var into the shell-exec sandbox.

| Method | Placet path                  | Upstream route               | Description                                                       |
| ------ | ---------------------------- | ---------------------------- | ----------------------------------------------------------------- |
| GET    | `/credentials`               | `/credentials`               | List keys (values masked) + `exposed` flags                       |
| POST   | `/credentials`               | `/credentials`               | Create a secret (`{key, value, exposed?}`); `409` when key exists |
| GET    | `/credentials/{key}`         | `/credentials/{key}`         | Existence check                                                   |
| PUT    | `/credentials/{key}`         | `/credentials/{key}`         | Upsert (`{"value": "..."}`)                                       |
| PUT    | `/credentials/{key}/exposed` | `/credentials/{key}/exposed` | Toggle exec-sandbox exposure (`{"exposed": bool}`)                |
| DELETE | `/credentials/{key}`         | `/credentials/{key}`         | Remove                                                            |

#### LLM provider credentials

Provider API keys live under `config.providers.<name>.api_key`. OAuth-only providers (`github_copilot`, `openai_codex`) report `isOauth: true` and use a token store instead of `api_key`; for those, `POST` / `PUT` return `400` and the OAuth endpoints below must be used.

| Method | Placet path                                 | Upstream route                              | Description                                                 |
| ------ | ------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------- |
| GET    | `/credentials/providers`                    | `/credentials/providers`                    | List providers + `hasValue` / `isOauth` state               |
| POST   | `/credentials/providers`                    | `/credentials/providers`                    | Set `api_key` (create-only — `409` when already set)        |
| PUT    | `/credentials/providers/{name}`             | `/credentials/providers/{name}`             | Upsert `api_key` (`{"value": "..."}`)                       |
| DELETE | `/credentials/providers/{name}`             | `/credentials/providers/{name}`             | Clear `api_key`, or disconnect OAuth for OAuth providers    |
| POST   | `/credentials/providers/{name}/oauth/start` | `/credentials/providers/{name}/oauth/start` | Begin OAuth device flow (`github_copilot` only — see below) |
| GET    | `/credentials/providers/{name}/oauth/poll`  | `/credentials/providers/{name}/oauth/poll`  | Poll an in-progress OAuth flow (`?session_id=...`)          |

`POST /oauth/start` returns:

```json theme={null}
{
  "sessionId": "…",
  "mode": "device",
  "userCode": "ABCD-1234",
  "verificationUri": "https://github.com/login/device",
  "expiresIn": 900,
  "interval": 5
}
```

The agent polls the provider in the background; the client polls `GET /oauth/poll?session_id=…` every `interval` seconds and receives `{status: "pending" | "ok" | "error", error?, account?}`. On `ok` the OAuth token is persisted server-side (no value ever crosses the wire). `openai_codex` is intentionally **not** supported via REST — its OAuth client mandates a fixed `http://localhost:1455/auth/callback` redirect, so use your agent's local CLI to complete the OAuth login.

### Cron

| Method | Placet path          | Upstream route       | Description         |
| ------ | -------------------- | -------------------- | ------------------- |
| GET    | `/cron`              | `/cron`              | List jobs           |
| POST   | `/cron`              | `/cron`              | Create              |
| GET    | `/cron/{id}`         | `/cron/{id}`         | Get single job      |
| PATCH  | `/cron/{id}`         | `/cron/{id}`         | Partial update      |
| DELETE | `/cron/{id}`         | `/cron/{id}`         | Remove              |
| POST   | `/cron/{id}/run-now` | `/cron/{id}/run-now` | Trigger immediately |
| POST   | `/cron/{id}/pause`   | `/cron/{id}/pause`   | Disable             |
| POST   | `/cron/{id}/resume`  | `/cron/{id}/resume`  | Enable              |

### MCP servers

| Method | Placet path           | Upstream route        | Description          |
| ------ | --------------------- | --------------------- | -------------------- |
| GET    | `/mcp`                | `/mcp`                | List servers         |
| POST   | `/mcp`                | `/mcp`                | Add a server         |
| GET    | `/mcp/{name}`         | `/mcp/{name}`         | Get single server    |
| PATCH  | `/mcp/{name}`         | `/mcp/{name}`         | Edit config          |
| DELETE | `/mcp/{name}`         | `/mcp/{name}`         | Remove               |
| POST   | `/mcp/{name}/enable`  | `/mcp/{name}/enable`  | Enable + connect     |
| POST   | `/mcp/{name}/disable` | `/mcp/{name}/disable` | Disable + disconnect |
| POST   | `/mcp/{name}/restart` | `/mcp/{name}/restart` | Reconnect            |

### Tool policy

Persistent allow/deny rules for tool calls plus runtime toggles for the approval gate. `deny` wins over `allow`; missing rules trigger a one-time approval prompt unless the gate is disabled or skipped for cron callers.

| Method | Placet path        | Upstream route     | Description                                                                    |
| ------ | ------------------ | ------------------ | ------------------------------------------------------------------------------ |
| GET    | `/policy`          | `/policy`          | List rules + current `enabled` / `skipCron` flags and on-disk policy file path |
| POST   | `/policy`          | `/policy`          | Add a rule (`{action: 'allow' \| 'deny', tool, params?}`)                      |
| DELETE | `/policy`          | `/policy`          | Remove a single rule (same body shape as `POST`)                               |
| DELETE | `/policy/all`      | `/policy/all`      | Clear all rules                                                                |
| PATCH  | `/policy/settings` | `/policy/settings` | Update runtime flags (`{enabled?: boolean, skipCron?: boolean}`)               |

The `enabled` flag is the master switch for the approval gate — when off, every tool call runs without prompts and the rule list is ignored. `skipCron` lets cron-triggered tool calls bypass the approval prompt while keeping the gate active for interactive callers.

### Workspace

| Method | Placet path       | Upstream route    | Description                         |
| ------ | ----------------- | ----------------- | ----------------------------------- |
| GET    | `/workspace/tree` | `/workspace/tree` | Directory listing (`?path=&depth=`) |
| GET    | `/workspace/file` | `/workspace/file` | Read file contents (`?path=`)       |
| PUT    | `/workspace/file` | `/workspace/file` | Write file contents (`?path=`)      |
| DELETE | `/workspace/file` | `/workspace/file` | Delete file (`?path=`)              |

### Skills & scripts

| Method | Placet path | Upstream route | Description                  |
| ------ | ----------- | -------------- | ---------------------------- |
| GET    | `/skills`   | `/skills`      | List workspace `skills/*.md` |
| GET    | `/scripts`  | `/scripts`     | List workspace `scripts/*`   |

### Channels

| Method | Placet path        | Upstream route     | Description                              |
| ------ | ------------------ | ------------------ | ---------------------------------------- |
| GET    | `/channels`        | `/channels`        | List channel configs                     |
| GET    | `/channels/{name}` | `/channels/{name}` | Get channel config                       |
| PUT    | `/channels/{name}` | `/channels/{name}` | Upsert channel config (restart required) |
| DELETE | `/channels/{name}` | `/channels/{name}` | Remove channel config (restart required) |

### Commands

| Method | Placet path         | Upstream route      | Description        |
| ------ | ------------------- | ------------------- | ------------------ |
| POST   | `/commands/stop`    | `/commands/stop`    | Execute `/stop`    |
| POST   | `/commands/restart` | `/commands/restart` | Execute `/restart` |
| POST   | `/commands/new`     | `/commands/new`     | Execute `/new`     |
| POST   | `/commands/reflect` | `/commands/reflect` | Execute `/reflect` |
| GET    | `/commands/status`  | `/commands/status`  | Execute `/status`  |

### Agent Card

| Method | Placet path   | Upstream route | Description             |
| ------ | ------------- | -------------- | ----------------------- |
| GET    | `/agent-card` | `/agent-card`  | Live A2A AgentCard JSON |

### A2A peers

| Method | Placet path               | Upstream route            | Description                     |
| ------ | ------------------------- | ------------------------- | ------------------------------- |
| GET    | `/a2a/peers`              | `/a2a/peers`              | List registered peers           |
| POST   | `/a2a/peers`              | `/a2a/peers`              | Register / overwrite a peer     |
| DELETE | `/a2a/peers/{alias}`      | `/a2a/peers/{alias}`      | Remove a peer                   |
| GET    | `/a2a/peers/{alias}/card` | `/a2a/peers/{alias}/card` | Fetch the peer Agent Card       |
| POST   | `/a2a/peers/{alias}/call` | `/a2a/peers/{alias}/call` | Send a message through the peer |

### Error semantics

| Status | Meaning                                                                                                                                       |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing/invalid Placet JWT.                                                                                                                   |
| `404`  | Agent has no management credentials configured.                                                                                               |
| `502`  | Upstream timed out (15 s) or is unreachable. The frontend hides the dashboard silently.                                                       |
| other  | Upstream errors are normalized to `{ error: { code: 'upstream_<status>', message }, upstreamStatus }` and re-emitted at the same HTTP status. |

***

## Minimal Upstream Requirements

To support the management dashboard, your agent runtime **must**:

1. Expose a bearer-authenticated management API on `/api/v1/*` that matches the wire contract documented above.
2. Implement any subset of the routes above that you want users to see; unsupported ones simply return `404`, which the dashboard handles gracefully.
3. Accept a long-lived management bearer token that you can transmit to Placet via `setWebhook`/`setManagement`.

If a domain (e.g. `cron`, `mcp`) isn't supported by your runtime, the Placet UI tab for that feature stays empty — there is nothing else you need to do to "opt out".

***

## Security Notes

* Management keys are stored alongside webhook secrets and returned as `***` on every agent read.
* Only the agent owner (owner of the `apiKey` that registered the channel) can call `/manage/*` for that agent.
* Rotate a key by calling `setManagement` again; to revoke, set both fields to `null`.
