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


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