Agents and runs
An agent is an autonomous worker with its own isolated runtime. You create agents from a persona or a catalog template, manage their runtime lifecycle, configuration, and secrets, then dispatch work to them. A run is one execution of an agent; a run that pauses on a guardrail creates an approval a human answers before the run resumes.
Base URL: https://api.perceive8.com. Authenticate with X-API-Key: pk_live_... or Authorization: Bearer <jwt-or-key>. All endpoints are workspace-scoped: pass X-Workspace-Id (or the workspace_id query parameter); when omitted, your Personal workspace is used. {agent_id} and {run_id} path parameters are UUIDs.
Scopes
| Scope | Required for |
|---|---|
mcp:agents:read |
Reading agents, runtimes, logs, configs, secret names, runs, approvals, templates |
mcp:agents:run |
Dispatching runs; the agent webhook |
mcp:agents:write |
Agent mutations, runtime lifecycle, config and secret writes, approval responses |
API keys must carry the listed scope (or *). User-JWT callers bypass read scopes, but write operations require an admin or owner role in the workspace.
Agents
The agent object:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Agent ID |
workspace_id |
string (UUID) | Owning workspace |
summoro_agent_id |
string | null | Orchestrator-side ID; null until synced |
name |
string | Display name |
persona |
string | System prompt / persona |
default_model_tier |
string | Model tier (for example small) |
skills |
string[] | Enabled skills |
has_sensitive_tools |
boolean | True when skills include sensitive integrations (google, microsoft, hubspot, notion, n8n, whatsapp, telegram, calls) |
runtime_kind |
string | Runtime kind, for example openclaw |
deployment_status |
string | pending, published, or failed |
guardrails |
object | Guardrail configuration |
created_at, updated_at |
string | Timestamps (list/get responses only) |
archived_at |
string | null | Set when archived |
GET /v1/agents
List non-archived agents in the workspace, newest first. Response: an array of agent objects.
curl https://api.perceive8.com/v1/agents \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-H "X-Workspace-Id: <workspace-uuid>"
POST /v1/agents
Create an agent and mirror it to the orchestrator. Requires mcp:agents:write (JWT: admin/owner).
Request body
{
"name": "Support triager",
"template_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"business_description": "B2B SaaS helpdesk, 40 seats",
"communication_styles": ["concise"],
"channels": ["email"],
"skills": [],
"guardrails": {}
}
| Field | Type | Description |
|---|---|---|
name |
string (required, 1–120 chars) | Agent name |
workspace_id |
string | Optional; if sent, must match the active workspace |
persona |
string | System prompt; when omitted with template_id, compiled from the template |
template_id |
string (UUID) | Create from a catalog template (see GET /v1/agent-templates) |
business_description, communication_styles |
string / string[] | Template inputs folded into the compiled persona |
channels |
string[] | Channels the agent operates on |
model_tier / default_model_tier |
string | Model tier; defaults to the template tier, then small |
skills |
string[] | Skill slugs; defaults to the template's default_skills |
guardrails |
object | Guardrail configuration |
Template-based creation compiles persona, skills, and tier from the template and fails with 400 if the template yields no skills. New agents get runtime_kind: "openclaw". Response: the agent object (without created_at/updated_at).
GET /v1/agents/{agent_id}
Get a single agent; 404 when archived or outside the workspace. Response: the agent object.
PATCH /v1/agents/{agent_id}
Update an agent and mirror the change. Requires mcp:agents:write (JWT: admin/owner). Archived agents cannot be updated (400).
Request body — all fields optional:
{
"name": "Support triager v2",
"persona": "You are a support triage assistant...",
"default_model_tier": "medium",
"skills": ["web_search", "email"],
"guardrails": {}
}
Response: the updated agent object (without created_at/updated_at).
POST /v1/agents/{agent_id}/archive
Archive an agent and tear down its runtime. Requires mcp:agents:write. Response: the agent object with archived_at set.
Runtime lifecycle
GET /v1/agents/{agent_id}/runtime
Get current runtime status; null when the agent is not synced yet. The response is the orchestrator's runtime record:
| Field | Type | Description |
|---|---|---|
agent_id, workspace_id |
string | Owning IDs |
runtime_kind |
string | Runtime kind |
status |
string | Runtime state (for example started, suspended) |
fly_app, fly_machine_id, fly_region |
string | Compute placement |
fly_public_url |
string | Public edge URL of the runtime |
cpu_kind, cpu_count, memory_mb |
string / number | Machine size |
image_ref |
string | Deployed image |
last_seen_at, last_error |
string | null | Health and last error |
created_at, updated_at |
string | Timestamps |
POST /v1/agents/{agent_id}/runtime/provision
Provision the runtime. Requires mcp:agents:write.
Request body — all fields optional; runtime_kind and secrets are accepted but currently only region and image_ref are applied:
{ "region": "iad", "image_ref": "registry.fly.io/p8-runtime:latest" }
Response
{
"agent_id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"status": "created",
"fly_app": "p8-agent-2b3c4d5e",
"fly_machine_id": "5683dd9c12e989",
"fly_private_host": "p8-agent-2b3c4d5e.internal",
"fly_public_url": "https://p8-agent-2b3c4d5e.fly.dev",
"error": null
}
POST /v1/agents/{agent_id}/runtime/redeploy
Destroy and reprovision the runtime. Requires mcp:agents:write. Response: same shape as provision.
POST /v1/agents/{agent_id}/runtime/suspend
Suspend the runtime. Requires mcp:agents:write. Response: { "ok": true }.
DELETE /v1/agents/{agent_id}/runtime
Destroy the runtime. Requires mcp:agents:write. Response: { "ok": true }.
POST /v1/agents/{agent_id}/republish
Retry a failed or destroyed runtime: redeploys when the agent is synced, otherwise provisions from scratch. Requires mcp:agents:write. Response: same shape as provision.
GET /v1/agents/{agent_id}/logs
Fetch runtime logs on demand. Logs are not stored by Perceive8; the list is empty when the orchestrator has none.
| Parameter | In | Description |
|---|---|---|
limit |
query | Max entries, default 100 |
Response: { "logs": [...], "nextToken": null } — log entry objects as returned by the orchestrator.
Configuration
GET /v1/agents/{agent_id}/config/{config_type}
Get agent config by type. env is read from local storage; other types are best-effort reads from the orchestrator and return {} when unavailable. Response: the config object, for example { "LOG_LEVEL": "debug" }.
PUT /v1/agents/{agent_id}/config/{config_type}
Set agent config. env config is persisted locally and mirrored to the orchestrator; other types are written to the orchestrator only. Requires mcp:agents:write.
Request body: { "config": { "LOG_LEVEL": "debug" } }
Response: the stored config object.
Secrets
Secret values are write-only — they are stored on the agent's runtime and can never be read back. Only secret names are listed.
GET /v1/agents/{agent_id}/secrets
List an agent's secret names. Response: [{ "secret_name": "HUBSPOT_API_KEY" }].
PUT /v1/agents/{agent_id}/secrets/{secret_name}
Set a secret on the agent's runtime. Requires mcp:agents:write. secret_name must match ^[A-Za-z_][A-Za-z0-9_]*$.
Request body: { "value": "pat-na1-xxxx" }
Response: { "secret_name": "HUBSPOT_API_KEY" }.
DELETE /v1/agents/{agent_id}/secrets/{secret_name}
Delete a secret from the runtime. Requires mcp:agents:write. Response: { "ok": true }.
Webhook
POST /v1/agents/{agent_id}/webhook
Proxy an inbound webhook to the agent's runtime. Requires mcp:agents:run; 400 when the agent is not synced. The body is an arbitrary JSON object forwarded verbatim; the response is whatever the runtime returns.
Catalog
GET /v1/agent-templates
List templates visible to the workspace (system templates plus the caller's own), ordered by usage.
Response — array of template objects:
| Field | Type | Description |
|---|---|---|
id, slug, name |
string | Identity |
description, category |
string | Catalog metadata |
persona, system_prompt |
string | Prompt material used to compile new agents |
default_skills |
string[] | Skills applied to new agents |
communication_style |
string | Default style hint |
default_model_tier |
string | Default tier |
runtime_kind |
string | Runtime kind |
is_system |
boolean | Built-in template |
usage_count |
number | Times used |
created_at, updated_at |
string | Timestamps |
GET /v1/runtime-kinds
List available runtime kinds.
Response — array of objects with fields runtime_kind, label, description, image_ref (all strings).
Runs
The run object:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Local run ID |
agent_id, workspace_id |
string (UUID) | Owning agent and workspace |
task_id |
number | null | Linked board task |
status |
string | For example queued, running, done, failed, cancelled |
summoro_run_id |
string | null | Orchestrator-side run ID |
trigger |
object | Trigger payload; type is api unless overridden |
started_at, ended_at |
string | null | Filled from live status when available |
error, provider, model, tokens_in, tokens_out, zdr_eligible, used_free_tier |
— | Reserved; currently null |
created_at, updated_at |
string | Timestamps |
POST /v1/runs
Dispatch a run. Requires mcp:agents:run. The agent must belong to the workspace and be synced (404/502 otherwise). Dispatch is synchronous: the response carries the terminal result.
| Parameter | In | Description |
|---|---|---|
stream |
query | 1 or true to receive the result as an SSE stream instead of JSON |
Request body
{
"agent_id": "5f1c9c2e-2a4b-4e0d-9f3a-1b2c3d4e5f60",
"message": "Summarize this week's inbound leads",
"model": { "tier": "medium" }
}
| Field | Type | Description |
|---|---|---|
agent_id |
string (UUID, required) | Agent to run |
message |
string | Single user message |
messages |
object[] | Full message history; overrides message when given |
trigger |
object | Trigger metadata; defaults to { "type": "api" }. The workspace ID is injected server-side |
model |
object | Model override, for example { "tier": "small" | "medium" | "expert" | "free" }. Defaults to the agent's configured tier |
Response (default, JSON)
{
"run_id": "7d8e9f0a-1b2c-3d4e-5f6a-7b8c9d0e1f2a",
"local_run_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"status": "done",
"agent_id": "5f1c9c2e-2a4b-4e0d-9f3a-1b2c3d4e5f60",
"workspace_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"trigger": { "type": "api", "workspace_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d" }
}
Response (with ?stream=true) — text/event-stream. Events arrive in one batch after the run completes; on dispatch failure the stream carries error + status: failed instead of an HTTP error.
| Event | When sent | Payload |
|---|---|---|
message |
The run produced an assistant reply | The assistant message object |
metrics |
The run produced metrics | The metrics object |
error |
The run failed | { "message": string } |
status |
Always, last | { "status": string } — failed after an error event |
GET /v1/runs
List local runs for an agent or board task, enriched with live status. Requires mcp:agents:read; 400 when neither filter is given.
| Parameter | In | Description |
|---|---|---|
agent_id |
query | Agent UUID (this or task_id required) |
task_id |
query | Board task ID |
limit |
query | Max rows, default 50 |
Response: array of run objects, newest first.
GET /v1/runs/{run_id}
Get a single run, enriched with live status. Response: the run object.
GET /v1/runs/{run_id}/messages
Get the run's message history; empty array when unavailable. Response: array of message objects ordered by seq, each with at least role, content (for example { "text": "..." }), and seq.
Approvals
A run pauses when a guardrail requires human sign-off; the approval names the tool and arguments under review. Responding resumes the paused run.
The approval object:
| Field | Type | Description |
|---|---|---|
id |
string | Approval ID |
run_id |
string | Paused run |
workspace_id, agent_id |
string | Owning workspace and agent |
tool_name |
string | Tool awaiting approval |
arguments |
object | Proposed tool arguments |
resume_state |
object | State used to resume the run |
status |
string | pending, approved, or rejected |
comment |
string | null | Responder's comment |
responded_at, responded_by |
string | null | Response metadata |
created_at, updated_at |
string | Timestamps |
GET /v1/approvals
List approvals in the workspace, newest first. Requires mcp:agents:read.
| Parameter | In | Description |
|---|---|---|
status |
query | Filter by status, default pending |
Response: array of approval objects.
POST /v1/approvals/{approval_id}/respond
Approve or reject a pending approval. Requires mcp:agents:write (JWT: admin/owner). Responding resumes the paused run.
Request body
{
"decision": "approved",
"comment": "Looks correct",
"answer": "Use the Q3 pricing sheet"
}
| Field | Type | Description |
|---|---|---|
decision |
string (required) | approved or rejected |
comment |
string | Optional comment stored on the approval |
answer |
string (max 2000 chars) | Optional free-text answer forwarded to the run for question-style approvals |
Response: { "id": "<approval-id>", "status": "approved" }.