docs/content/docs/api-reference.mdx
Molecule AI App & Docs Lead 703883b871 merge: PR #39 workspace hibernation docs
Conflict resolved in content/docs/concepts.mdx:
- Keep main's AGENTS.md auto-generation + workspace budgets section
- Add pr-39's workspace status lifecycle table + hibernation note

Both changes are additive and non-overlapping in scope.

🤖 Generated with [Claude Code](https://claude.ai/claude-code)
2026-04-20 22:07:07 +00:00

755 lines
32 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: API Reference
description: Complete reference for all Molecule AI Platform HTTP and WebSocket endpoints.
---
# API Reference
The Molecule AI Platform exposes a REST API (default port 8080) for workspace management, agent registry, communication, and administration. All endpoints return JSON unless otherwise noted.
<Callout type="warn">
**Breaking changes — PR #701 (2026-04-17)**
- **`PATCH /workspaces/:id` now requires authentication.** Previously, requests without a bearer token could update cosmetic fields (name, x/y position). All `PATCH` calls now require `Authorization: Bearer <workspace-token>` or receive **401 Unauthorized**.
- **`GET /templates` and `GET /org/templates` now require AdminAuth.** Unauthenticated callers receive **401 Unauthorized**.
- **All `/workspaces/:id` endpoints validate the `:id` path parameter** as a UUID. Non-UUID values return **400 Bad Request** before any database interaction.
**Migration:** add `Authorization: Bearer <workspace-token>` to all `PATCH /workspaces/:id` calls. Use an admin bearer token for `GET /templates` and `GET /org/templates`. Ensure `:id` values in automation scripts are valid UUIDs.
</Callout>
**Base URL:** `http://localhost:8080` (self-hosted) or `https://api.moleculesai.app` (SaaS)
---
## Authentication Model
The platform uses three authentication middleware variants depending on the sensitivity of the route.
### AdminAuth
Strict bearer-token authentication. Required for any route where a forged request could leak prompts/memory, create/mutate workspaces, or leak operational data.
```
Authorization: Bearer <token>
```
**Fail-open behavior:** When no live tokens exist globally (fresh install), AdminAuth passes all requests through. Once the first token is created, all AdminAuth routes require a valid bearer.
### WorkspaceAuth
Per-workspace bearer token binding. Workspace A's token cannot access workspace B's sub-routes. Used for the entire `/workspaces/:id/*` group (except the A2A proxy, which uses `CanCommunicate`).
```
Authorization: Bearer <workspace-token>
```
### CanvasOrBearer
Accepts either a valid bearer token OR a request whose `Origin` header matches `CORS_ORIGINS`. Used only for cosmetic-only routes where a forged request has zero data/security impact.
Currently applies only to `PUT /canvas/viewport`. Do not extend to data-sensitive routes.
---
## Health and Monitoring
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/health` | None | Returns `200 OK` if the platform is running. Use for load balancer health checks. |
| GET | `/metrics` | None | Prometheus text format (v0.0.4) metrics. Scrape-safe, no auth required. |
| GET | `/admin/liveness` | AdminAuth | Per-subsystem `supervised.Snapshot()` ages. Check before debugging stuck scheduler/heartbeat goroutines. |
---
## Workspaces
Core workspace CRUD and lifecycle operations.
### CRUD
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces` | AdminAuth | Create a new workspace. Accepts `name`, `runtime`, `template`, `parent_id`, `tier`, `workspace_dir`, and other fields. Runtime is auto-detected from template config if omitted (defaults to `langgraph`). |
| GET | `/workspaces` | AdminAuth | List all workspaces with status, runtime, agent card, position, and hierarchy info. |
| GET | `/workspaces/:id` | WorkspaceAuth | Get a single workspace by ID. |
| PATCH | `/workspaces/:id` | WorkspaceAuth | Update workspace fields. A workspace bearer token is always required — unauthenticated calls return 401. Validates field constraints: `name` ≤ 255 chars, `role` ≤ 1,000 chars, `model` and `runtime` ≤ 100 chars each; `name` and `role` must not contain newlines (`\\n`, `\\r`) or YAML-special characters (`{}[]|>*&!`). Oversized or invalid field values return 400. `:id` must be a valid UUID. Financial fields (`budget_limit`) are not accepted here — use `PATCH /workspaces/:id/budget` (AdminAuth). |
| DELETE | `/workspaces/:id` | AdminAuth | Delete a workspace. Stops the container, revokes all auth tokens, and removes all associated data. |
### Lifecycle
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/restart` | WorkspaceAuth | Restart the workspace container. Sends a `restart_context` A2A message after successful re-registration. |
| POST | `/workspaces/:id/pause` | WorkspaceAuth | Stop the container and set status to `paused`. Paused workspaces skip health sweep, liveness monitor, and auto-restart. Resume manually via `/resume`. |
| POST | `/workspaces/:id/resume` | WorkspaceAuth | Re-provision a paused workspace. Status transitions to `provisioning`. |
| POST | `/workspaces/:id/hibernate` | WorkspaceAuth | Immediately hibernate a workspace (stop container, set status to `hibernated`). Useful for manual cost control. See hibernation note below. |
<Callout type="info">
**Workspace hibernation**
A workspace with `hibernation_idle_minutes` set in its config will be **automatically hibernated** by the platform after that many idle minutes (no active tasks, no recent heartbeat). The monitor checks every 2 minutes.
`hibernated` differs from `paused`:
- **`paused`** — manual, resumes only via `POST /resume`.
- **`hibernated`** — automatic (or via `POST /hibernate`), resumes **automatically** when an A2A message arrives.
When a message is sent to a hibernated workspace, the platform returns:
```
HTTP 503 Retry-After: 15
{"waking": true}
```
Callers should retry after ~15 seconds. The workspace typically returns to `online` within that window.
To opt a workspace into auto-hibernation, add to its `config.yaml`:
```yaml
hibernation_idle_minutes: 30 # hibernate after 30 min idle; null (default) = disabled
```
**Atomic hibernation guarantee:** The platform uses a single atomic SQL claim (`UPDATE … WHERE active_tasks = 0`) before stopping the container. If a task arrives between the idle check and the container stop, the claim fails and hibernation is aborted — no in-flight tasks are silently lost.
</Callout>
### Budget
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/budget` | AdminAuth | Read a workspace's current spend and ceiling. Returns `budget_limit`, `monthly_spend`, and `budget_remaining` (all in USD cents). |
| PATCH | `/workspaces/:id/budget` | AdminAuth | Set or clear a workspace's monthly spend ceiling. Body: `{ "budget_limit": N }` (positive integer, USD cents) or `{ "budget_limit": null }` to remove the cap. Negative values → 400. Returns same shape as GET. |
**Request / response shape:**
```json
// PATCH request body
{ "budget_limit": 500 } // $5.00/month ceiling
{ "budget_limit": null } // no ceiling
// GET and PATCH success response (200)
{
"budget_limit": 500, // null when no ceiling
"monthly_spend": 312, // accumulated spend this period, USD cents
"budget_remaining": 188 // null when no ceiling; max(0, limit-spend) — can be negative
}
```
<Callout type="warn">
**`budget_limit` and `monthly_spend` are absent from `GET /workspaces/:id`**
Financial fields are stripped unconditionally from the workspace detail
response — they do not appear for any caller, authenticated or not. Always
use `GET /workspaces/:id/budget` (AdminAuth) to read spend data.
`budget_limit` is also **not** accepted on the general `PATCH /workspaces/:id`
endpoint. Use the dedicated `/budget` route.
</Callout>
<Callout type="info">
**Enforcement and fail-open behaviour**
When `monthly_spend >= budget_limit`, `POST /workspaces/:id/a2a` returns:
```
HTTP 402 Payment Required
{"error": "workspace budget limit exceeded"}
```
Channel sends (Slack, Telegram, Discord, Lark) are also budget-gated with
the same 402 response. The workspace itself is **not paused** — it keeps
running; only inbound A2A and channel traffic is blocked.
**Fail-open:** if the budget check encounters a DB error, traffic is allowed
through rather than blocked. The spend ceiling is a soft guardrail, not a
hard guarantee.
</Callout>
---
## Registry
Workspace registration and heartbeat endpoints. Called by workspace runtimes, not by end users.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/registry/register` | None | Register a workspace with the platform. Sets status to `online`. Body includes agent URL, agent card, capabilities. |
| POST | `/registry/heartbeat` | Bearer (if token exists) | Send a heartbeat. Updates Redis TTL key (60s expiry). Body can include `active_tasks`, `current_task`, `error_rate`. Triggers `degraded` status if `error_rate > 0.5`. |
| POST | `/registry/update-card` | Bearer (if token exists) | Update the workspace's agent card (name, description, skills, etc.). |
---
## Discovery
Peer discovery and access control verification.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/registry/discover/:id` | Bearer + `X-Workspace-ID` | Discover a workspace's agent card and URL. Requires caller identification. Fails open on DB hiccup since hierarchy check is primary. |
| GET | `/registry/:id/peers` | Bearer + `X-Workspace-ID` | List all peers (siblings, parent, children) that the caller can communicate with. |
| POST | `/registry/check-access` | None | Check whether two workspaces can communicate. Body: `{ "caller_id": "...", "target_id": "..." }`. Returns `{ "allowed": true/false }`. |
---
## Communication
### A2A Proxy
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/a2a` | CanCommunicate | Proxy an A2A JSON-RPC message to the target workspace. Caller identified via `X-Workspace-ID` header. Canvas requests (no header) bypass access check. On connection error, checks if container is dead and triggers auto-restart. |
### Delegation
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/delegate` | WorkspaceAuth | Async fire-and-forget delegation. Supports idempotency keys. Body includes target workspace, prompt, and metadata. |
| GET | `/workspaces/:id/delegations` | WorkspaceAuth | List delegation status for a workspace. Returns delegation rows with status, result, timestamps. |
---
## Configuration
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/config` | WorkspaceAuth | Get the workspace's `config.yaml` contents. |
| PATCH | `/workspaces/:id/config` | WorkspaceAuth | Update the workspace config. "Save & Restart" writes config and auto-restarts; "Save" writes only and shows a restart banner in the Canvas. |
---
## Secrets
### Per-Workspace Secrets
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/secrets` | WorkspaceAuth | List secret keys for a workspace (keys only, values masked). |
| POST | `/workspaces/:id/secrets` | WorkspaceAuth | Set a secret `{ "key": "...", "value": "..." }`. Auto-restarts the workspace. |
| PUT | `/workspaces/:id/secrets` | WorkspaceAuth | Alias for POST (upsert semantics). Auto-restarts the workspace. |
| DELETE | `/workspaces/:id/secrets/:key` | WorkspaceAuth | Delete a secret by key. Auto-restarts the workspace. |
| GET | `/workspaces/:id/model` | WorkspaceAuth | Return the model configuration derived from available API keys (which provider keys are set). |
### Global Secrets
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/settings/secrets` | AdminAuth | List global secrets (keys only, values masked). |
| PUT | `/settings/secrets` | AdminAuth | Set a global secret `{ "key": "...", "value": "..." }`. Auto-restarts every non-paused/non-removed workspace that does not shadow the key with a workspace-level override. |
| POST | `/settings/secrets` | AdminAuth | Alias for PUT. |
| DELETE | `/settings/secrets/:key` | AdminAuth | Delete a global secret. Same auto-restart fan-out as PUT. |
Legacy aliases `GET/POST/DELETE /admin/secrets[/:key]` also exist and behave identically.
---
## Memory
### Key-Value Memory
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/memory` | WorkspaceAuth | List all key-value memory entries for a workspace. |
| POST | `/workspaces/:id/memory` | WorkspaceAuth | Set a memory entry `{ "key": "...", "value": "..." }`. |
| DELETE | `/workspaces/:id/memory/:key` | WorkspaceAuth | Delete a memory entry by key. |
### Agent Memories (HMA-scoped)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/memories` | WorkspaceAuth | List or search agent memories. Supports `?q=` for semantic search (see below). |
| POST | `/workspaces/:id/memories` | WorkspaceAuth | Create an agent memory entry. |
| DELETE | `/workspaces/:id/memories/:id` | WorkspaceAuth | Delete an agent memory by ID. |
#### Semantic search (`?q=`)
When a platform-level embedding function is configured, passing `?q=<text>`
on `GET /workspaces/:id/memories` triggers vector similarity search instead of
the default full-text / ILIKE path:
```
GET /workspaces/{id}/memories?q=authentication+flow&limit=10
Authorization: Bearer {token}
```
Matching entries are returned **ordered by cosine similarity** (most similar
first). Each row includes an additional `similarity_score` field (01, higher
is closer):
```json
[
{
"id": "mem_abc123",
"key": "auth-design",
"value": "We use short-lived JWTs issued by the platform and refreshed via /auth/token.",
"similarity_score": 0.91,
"created_at": "2026-04-10T14:22:00Z"
}
]
```
**Graceful fallback**: if no embedding function is configured, or if the
embedding call fails for a given query, the platform falls back transparently
to the text-search path. The `similarity_score` field is absent in fallback
responses. You do not need to change client code to handle both modes.
---
## Files
Workspace file management. Files are stored in the workspace's config directory.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/files` | WorkspaceAuth | List files in the workspace config directory. |
| GET | `/workspaces/:id/files/*path` | WorkspaceAuth | Read a specific file. |
| PUT | `/workspaces/:id/files/*path` | WorkspaceAuth | Write a file. Creates parent directories as needed. |
| DELETE | `/workspaces/:id/files/*path` | WorkspaceAuth | Delete a file. |
| GET | `/workspaces/:id/shared-context` | WorkspaceAuth | Get the shared context files for a workspace (aggregated from parent hierarchy). |
---
## Activity
Activity logging and search for A2A communications, task updates, and agent logs.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/activity` | WorkspaceAuth | List activity logs for a workspace. Supports `?source=canvas` or `?source=agent` filter, and `?type=delegation` for A2A topology overlay polling. |
| POST | `/workspaces/:id/activity` | WorkspaceAuth | Log an activity entry (used by workspace runtimes to self-report). |
| POST | `/workspaces/:id/notify` | WorkspaceAuth | Agent-to-user push message via WebSocket. Delivers a notification to connected Canvas clients. |
---
## Audit Ledger
Tamper-evident audit trail for workspace events. Used by the Canvas Audit Trail panel.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/audit` | WorkspaceAuth | List audit entries for a workspace. Supports `?event_type=delegation\|decision\|gate\|hitl`, `?cursor=<cursor>`, and `?limit=<n>` (default 50). |
### Audit entry schema
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique entry ID |
| `event_type` | string | `delegation`, `decision`, `gate`, or `hitl` |
| `actor` | string | Workspace ID that generated the event |
| `summary` | string | Human-readable event description |
| `chain_valid` | bool | `false` if the entry's hash does not match the prior chain — indicates possible tampering |
| `created_at` | string (ISO 8601) | Event timestamp |
| `cursor` | string \| null | Opaque pagination cursor; `null` when there are no more entries |
Example response:
```json
{
"entries": [
{
"id": "aud_xyz789",
"event_type": "delegation",
"actor": "ws_abc123",
"summary": "Delegated task 'fix CI' to Backend Engineer",
"chain_valid": true,
"created_at": "2026-04-17T14:05:00Z"
}
],
"cursor": "eyJpZCI6ImF1ZF94eXo3ODkifQ"
}
```
### Session Search
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/session-search` | WorkspaceAuth | Search activity logs with filters for type, date range, and text content. Returns paginated results. |
---
## Workflow Checkpoints
Step-level progress persistence for long-running Temporal workflows. Workspaces with `runtime: langgraph` (Temporal) automatically save a checkpoint after each of the three workflow stages (`task_receive`, `llm_call`, `task_complete`) and resume from the last completed stage on restart.
<Callout type="info">
**Automatic resume behavior (runtime: langgraph only)**
When a Temporal workspace restarts mid-workflow, the runtime reads the highest-index checkpoint and sets `resume_from_step` accordingly. Already-completed stages are skipped — the agent picks up exactly where it left off without re-running earlier steps.
Checkpoint I/O is non-fatal: network errors are silently swallowed. A crashed or unreachable platform never prevents the agent from running.
</Callout>
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/checkpoints` | WorkspaceAuth | Upsert a step checkpoint. Body: `{ "workflow_id": "...", "step_name": "task_receive\|llm_call\|task_complete", "step_index": 0, "payload": {...} }`. Uses `ON CONFLICT DO UPDATE` — safe to call multiple times. |
| GET | `/workspaces/:id/checkpoints/:wfid` | WorkspaceAuth | Return all checkpoints for a workflow, ordered by `step_index DESC`. Returns 404 if no checkpoints exist for the workflow. |
| DELETE | `/workspaces/:id/checkpoints/:wfid` | WorkspaceAuth | Clear all checkpoints for a workflow. Called by the runtime on clean task completion. Returns 404 if none exist. |
**Step names and indices:**
| Step | `step_index` | Meaning |
|------|-------------|---------|
| `task_receive` | 0 | Task received from A2A message |
| `llm_call` | 1 | LLM inference completed |
| `task_complete` | 2 | Task result sent back to caller |
---
## Schedules
Cron-based scheduled tasks per workspace.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/schedules` | WorkspaceAuth | List all schedules for a workspace. |
| POST | `/workspaces/:id/schedules` | WorkspaceAuth | Create a schedule. Body: `{ "expression": "0 */6 * * *", "timezone": "UTC", "prompt": "...", "enabled": true }`. |
| PATCH | `/workspaces/:id/schedules/:scheduleId` | WorkspaceAuth | Update a schedule (expression, timezone, prompt, enabled). |
| DELETE | `/workspaces/:id/schedules/:scheduleId` | WorkspaceAuth | Delete a schedule. |
| POST | `/workspaces/:id/schedules/:scheduleId/run` | WorkspaceAuth | Manually trigger a schedule immediately. |
| GET | `/workspaces/:id/schedules/:scheduleId/history` | WorkspaceAuth | List past runs for a schedule. Includes status (`success`, `error`, `skipped`) and `error_detail`. |
Schedule `source` field: `template` for org/import-seeded schedules, `runtime` for Canvas/API-created. The `last_status` includes `skipped` when the scheduler concurrency-aware-skips a busy workspace.
---
## Channels
Social channel integrations (Telegram, Slack, etc.) for workspace agents.
### Per-Workspace Channels
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/channels` | WorkspaceAuth | List channels for a workspace. |
| POST | `/workspaces/:id/channels` | WorkspaceAuth | Create a channel. Body includes platform type, JSONB config, and allowlist. |
| PATCH | `/workspaces/:id/channels/:channelId` | WorkspaceAuth | Update a channel's config or allowlist. |
| DELETE | `/workspaces/:id/channels/:channelId` | WorkspaceAuth | Delete a channel. |
| POST | `/workspaces/:id/channels/:channelId/send` | WorkspaceAuth | Send an outbound message through the channel. |
| POST | `/workspaces/:id/channels/:channelId/test` | WorkspaceAuth | Test the channel connection (send a test message). |
### Global Channel Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/channels/adapters` | None | List available social platform adapters (Telegram, Slack, etc.). |
| POST | `/channels/discover` | AdminAuth | Auto-detect available chats/groups for a bot token. |
| POST | `/webhooks/:type` | None | Incoming webhook endpoint for social platforms. The `:type` parameter identifies the platform (e.g., `telegram`, `slack`). |
---
## Plugins
Plugin registry and per-workspace plugin management.
### Global Plugin Registry
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/plugins` | None | List all plugins in the registry. Supports `?runtime=` filter to show only compatible plugins. |
| GET | `/plugins/sources` | None | List registered install-source schemes (e.g., `github://`, `local://`). |
### Per-Workspace Plugins
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/plugins` | WorkspaceAuth | List installed plugins for a workspace. |
| POST | `/workspaces/:id/plugins` | WorkspaceAuth | Install a plugin. Body: `{ "source": "github://org/repo" }`. Safeguards: 64 KiB body limit, 5 min fetch timeout, 100 MiB max staged-tree. |
| DELETE | `/workspaces/:id/plugins/:name` | WorkspaceAuth | Uninstall a plugin by name. |
| GET | `/workspaces/:id/plugins/available` | WorkspaceAuth | List plugins available for this workspace (filtered by workspace runtime). |
| GET | `/workspaces/:id/plugins/compatibility` | WorkspaceAuth | Preflight runtime-change check. Query: `?runtime=X`. Returns which currently-installed plugins would be incompatible with the target runtime. |
---
## Auth Tokens
Bearer token management for workspaces.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/tokens` | WorkspaceAuth | List active tokens for a workspace (token values are masked). |
| POST | `/workspaces/:id/tokens` | WorkspaceAuth | Create a new bearer token for the workspace. |
| DELETE | `/workspaces/:id/tokens/:tokenId` | WorkspaceAuth | Revoke a specific token. |
### Test Token (Development Only)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/admin/workspaces/:id/test-token` | None | Mint a fresh bearer token for E2E scripts. Returns 404 unless `MOLECULE_ENV != production` or `MOLECULE_ENABLE_TEST_TOKENS=1`. |
---
## Teams
Expand and collapse team views in the Canvas hierarchy.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/expand` | WorkspaceAuth | Expand a team workspace to show its children on the canvas. |
| POST | `/workspaces/:id/collapse` | WorkspaceAuth | Collapse a team workspace to hide its children. |
---
## Templates and Bundles
### Templates
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/templates` | AdminAuth | List available workspace templates with their runtime, description, and config schema. |
| POST | `/templates/import` | AdminAuth | Import a workspace template from a `github://` source URL. |
### Org Templates
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/org/templates` | AdminAuth | List available organization templates. |
| POST | `/org/import` | AdminAuth | Import an org template. Applies `resolveInsideRoot` path sanitization. Creates the full workspace hierarchy defined in `org.yaml`. |
### Bundles
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/bundles/export/:id` | AdminAuth | Export a workspace (or workspace tree) as a portable bundle. Includes config, secrets (keys only), memory, schedules, and hierarchy. |
| POST | `/bundles/import` | AdminAuth | Import a previously-exported bundle. Recreates the workspace tree with all associated data. |
---
## Approvals
Human-in-the-loop approval system for agent actions.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/workspaces/:id/approvals` | WorkspaceAuth | Create an approval request. Body includes the action description, metadata, and options. |
| GET | `/workspaces/:id/approvals` | WorkspaceAuth | List approval requests for a workspace. |
| POST | `/workspaces/:id/approvals/:id/decide` | WorkspaceAuth | Approve or reject an approval request. Body: `{ "decision": "approve" }` or `{ "decision": "reject" }`. |
| GET | `/approvals/pending` | AdminAuth | List all pending approval requests across all workspaces. |
---
## Canvas
Canvas viewport persistence (cosmetic only).
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/canvas/viewport` | None | Get the saved canvas viewport (zoom, pan position). Open endpoint for bootstrap-friendliness. |
| PUT | `/canvas/viewport` | CanvasOrBearer | Save the canvas viewport. Accepts bearer OR matching `Origin` header. Worst case on forgery: viewport corruption, recovered by page refresh. |
---
## Traces
LLM trace retrieval from Langfuse.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/traces` | WorkspaceAuth | List LLM traces for a workspace from Langfuse. |
---
## Audit Ledger
HMAC-SHA256-chained immutable agent event log for compliance record-keeping (EU AI Act Art. 12 / Art. 13). Each event is cryptographically chained to the previous one — tampering with any record breaks all subsequent HMACs.
<Callout type="warn">
**`AUDIT_LEDGER_SALT` required.** The platform and workspace containers must share the same `AUDIT_LEDGER_SALT` environment variable to compute and verify event HMACs. Set it in both your platform env and workspace container env. If the variable is absent, `chain_valid` returns `null` (not `false`) — no records are lost, verification is simply unavailable.
</Callout>
### Query
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/audit` | WorkspaceAuth | Query the audit ledger for a workspace. Returns events in descending chronological order with inline chain verification. |
**Query parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `agent_id` | string | Filter to a specific agent. |
| `session_id` | string | Filter to a specific session. |
| `from` | RFC 3339 | Start of time range (e.g. `2026-04-01T00:00:00Z`). |
| `to` | RFC 3339 | End of time range. |
| `limit` | int | Max records to return. Capped at **500**. |
| `offset` | int | Pagination offset. |
**Response shape:**
```json
{
"events": [
{
"id": "uuid",
"workspace_id": "uuid",
"agent_id": "my-researcher",
"session_id": "sess_abc123",
"event_type": "tool_call",
"payload": { "tool": "bash", "input": "ls /workspace" },
"hmac": "sha256hex...",
"prev_hmac": "sha256hex...",
"created_at": "2026-04-17T12:00:00Z"
}
],
"chain_valid": true
}
```
`chain_valid` values:
- `true` — all HMACs verified; ledger is intact.
- `false` — at least one HMAC mismatch; possible tampering.
- `null` — `AUDIT_LEDGER_SALT` is absent from the platform env; verification skipped.
### Workspace-side: recording events
In your workspace template, wire `LedgerHooks` into the agent pipeline:
```python
from molecule_audit.hooks import LedgerHooks
hooks = LedgerHooks(agent_id="my-researcher", session_id=session_id)
async with hooks:
# hooks.on_task_start / on_llm_call / on_tool_call / on_task_end
# fire automatically at each pipeline stage
result = await agent.run(task)
```
`LedgerHooks` is exception-safe — a failed ledger write never aborts the agent task.
### CLI chain verification
```bash
# Verify the full chain for an agent; exit 0 = intact
python -m molecule_audit.verify --agent-id my-researcher
# Custom DB URL
python -m molecule_audit.verify --agent-id my-researcher --db postgresql://user:pass@host/db
```
Exit codes: `0` = chain valid · `1` = broken chain · `2` = `AUDIT_LEDGER_SALT` missing · `3` = DB error.
---
## Events
Append-only event log for structure changes.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/events` | AdminAuth | List all structure events across all workspaces. |
| GET | `/events/:workspaceId` | AdminAuth | List structure events for a specific workspace. |
---
## Terminal
WebSocket-based terminal access to workspace containers.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| WS | `/workspaces/:id/terminal` | WorkspaceAuth | Open a WebSocket terminal session to the workspace container. Provides interactive shell access. |
---
## WebSocket
Real-time event streaming for Canvas clients.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| WS | `/ws` | None | Connect to the WebSocket hub. Receives all structure events (`WORKSPACE_ONLINE`, `WORKSPACE_OFFLINE`, `HEARTBEAT`, `CONFIG_UPDATED`, `A2A_RESPONSE`, `AGENT_MESSAGE`, etc.). Canvas clients connect here for real-time updates. |
---
## Server-Sent Events (AG-UI)
Per-workspace SSE stream compatible with the [AG-UI protocol](https://github.com/ag-ui-protocol/ag-ui). Use this endpoint to consume structured agent events from a web client or external tool without a WebSocket library.
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/workspaces/:id/events/stream` | WorkspaceAuth | Open an SSE stream for the workspace. Returns `Content-Type: text/event-stream`. Sends an initial `: ping` comment on connect, then delivers every event emitted by the workspace in AG-UI envelope format. Events from other workspaces are filtered out. Returns `404` if the workspace does not exist. |
### Event envelope format
Each event is delivered as an SSE `data:` line containing a JSON object:
```json
{
"type": "AGENT_MESSAGE",
"timestamp": 1713398400000,
"data": { ... }
}
```
- **`type`** — event type string (e.g. `AGENT_MESSAGE`, `A2A_RESPONSE`, `TASK_UPDATED`)
- **`timestamp`** — Unix milliseconds at time of broadcast
- **`data`** — event-specific payload (same payload as the WebSocket hub delivers)
### Event types streamed
All event types emitted by `RecordAndBroadcast` **and** `BroadcastOnly` reach the SSE stream. The `BroadcastOnly` path is important: events like `AGENT_MESSAGE`, `A2A_RESPONSE`, and `TASK_UPDATED` skip Redis and would be invisible to a Redis-only subscriber — the in-process SSE layer catches them.
### Example: connect with `curl`
```bash
curl -N \
-H "Authorization: Bearer <workspace-token>" \
http://localhost:8080/workspaces/<id>/events/stream
```
```
: ping
data: {"type":"AGENT_MESSAGE","timestamp":1713398401234,"data":{"text":"Starting task..."}}
data: {"type":"TASK_UPDATED","timestamp":1713398405678,"data":{"status":"running"}}
```
### Example: connect from JavaScript
```js
const es = new EventSource(
`/workspaces/${workspaceId}/events/stream`,
{ headers: { Authorization: `Bearer ${token}` } }
);
es.onmessage = (e) => {
const event = JSON.parse(e.data);
console.log(event.type, event.data);
};
```
<Callout type="info">
The SSE endpoint uses WorkspaceAuth — the bearer token must be bound to the `:id` in the path. A token for workspace A cannot open a stream for workspace B.
</Callout>
---
## Error Responses
All endpoints return standard HTTP status codes:
| Status | Meaning |
|--------|---------|
| 200 | Success |
| 201 | Created |
| 400 | Bad request (malformed body, missing required fields) |
| 401 | Unauthorized (missing or invalid bearer token) |
| 403 | Forbidden (valid token but insufficient access) |
| 404 | Not found (workspace, schedule, channel, etc. does not exist) |
| 409 | Conflict (idempotency key collision on delegation) |
| 429 | Rate limited (exceeds `RATE_LIMIT` requests/min) |
| 500 | Internal server error |
Error response body format:
```json
{
"error": "human-readable error message"
}
```
---
## Rate Limiting
All endpoints are subject to a global rate limit of `RATE_LIMIT` requests per minute (default: 600). When exceeded, the platform returns `429 Too Many Requests` with a `Retry-After` header.
---
## CORS
The platform sets CORS headers based on the `CORS_ORIGINS` environment variable (comma-separated list, default: `http://localhost:3000,http://localhost:3001`). Preflight (`OPTIONS`) requests are handled automatically by the Gin CORS middleware.