Skip to main content
An agent is an AI coding agent with a configurable role, CLI adapter, skills, and credentials. Agents belonging to the same crew share one Linux container. These endpoints cover the full agent lifecycle: creating and configuring permanent agents, hiring short-lived ephemeral “contractors”, managing their skills and credentials, driving chats and runs, inspecting workspace files, and tuning the persona and self-learning layers that shape each agent’s behavior.
All /api/v1/agents/* endpoints require authentication and workspace context (the workspace_id query parameter). The chat-message proxy (/api/v1/chats/{chatId}/messages) is the exception — it resolves the workspace from the chat row and takes no workspace_id.

Endpoints

MethodEndpointPurpose
GET/api/v1/agents/crews-statusLightweight agent counts by status for toolbar/dashboard widgets
GET/api/v1/agent-loadAgent load metrics across the workspace
GET/api/v1/agentsList agents (optionally filtered by crew)
POST/api/v1/agentsCreate a new agent
GET/api/v1/agents/Get a single agent
PATCH/api/v1/agents/Partially update an agent
DELETE/api/v1/agents/Soft-delete an agent
POST/api/v1/agents/hireSpawn a new ephemeral agent under a crew
POST/api/v1/agents//rehireResurrect or extend an ephemeral agent
POST/api/v1/agents//approve-hireApprove a guided-autonomy pending hire
GET/api/v1/agents//personaGet the resolved persona
PUT/api/v1/agents//personaOperator-only direct persona write
DELETE/api/v1/agents//personaRemove the agent persona layer
GET/api/v1/agents//persona/historyList persona version history
POST/api/v1/agents//persona/suggestAgent-initiated persona proposal
GET/api/v1/agents//learningGet the self-learning flag and audit triple
PATCH/api/v1/agents//learningFlip the self-learning flag
GET/api/v1/agents//inboxConsolidated “what’s waiting on this agent” payload
GET/api/v1/agents//peersList the agent’s peer-card index
GET/api/v1/agents//peers/Get one peer card’s full content
DELETE/api/v1/agents//peers/Delete one peer card
GET/api/v1/agents//skillsList skill assignments
POST/api/v1/agents//skillsAssign a skill
DELETE/api/v1/agents//skills/Remove a skill
GET/api/v1/agents//credentialsList credential assignments
POST/api/v1/agents//credentialsAssign a credential
DELETE/api/v1/agents//credentials/Remove a credential
GET/api/v1/agents//chatsList chat sessions
POST/api/v1/agents//chatsCreate a new chat session
GET/api/v1/agents//runsList execution history
GET/api/v1/agents//debugDebug information for a running agent
GET/api/v1/agents//filesList files in agent workspace
GET/api/v1/agents//files/downloadDownload a file
PUT/api/v1/agents//files/saveSave/upload a file
GET/api/v1/agents//container-filesList files inside the running container
GET/api/v1/agents//git-logRecent git commits from the container workspace
GET/api/v1/agents//logsGet agent logs
POST/api/v1/agents//stopStop a running agent
GET/api/v1/chats//messagesGet messages for a chat session

Status & Metrics

Lightweight, read-only aggregates that power toolbar and dashboard widgets.

Crews Status

GET /api/v1/agents/crews-status?workspace_id={workspaceId}

# Response is a per-workspace aggregate counting agents by status
# (IDLE/RUNNING/ERROR — no STOPPED bucket). Drives the 'Crews idle/online'
# badge in the top toolbar.
Returns lightweight agent counts by status for toolbar/dashboard widgets. Response: 200 OK
{
  "total": 12,
  "running": 3,
  "error": 1,
  "idle": 8,
  "queued": 0
}
The agent status buckets (total, running, error, idle) are always present — zero counts are not omitted. Any status that is not RUNNING or ERROR (including IDLE and PENDING_REVIEW) folds into the idle bucket; there is no stopped bucket. The queued field counts admission-queue assignments (not agents) currently in the QUEUED state — independent of the agent buckets, and 0 on servers that pre-date the queue migration (internal/api/agents.go:111-151).

Agent Load

GET /api/v1/agent-load?workspace_id={workspaceId}
Returns agent load metrics across the workspace.

Lifecycle & CRUD

Create, read, update, and soft-delete permanent agents.

List Agents

GET /api/v1/agents?workspace_id={workspaceId}
Query Parameters:
ParameterTypeDescription
workspace_idstringRequired. Workspace ID
crew_idstringOptional. Filter by crew
Response: 200 OK
[
  {
    "id": "agent_abc",
    "crew_id": "crew_123",
    "workspace_id": "ws_456",
    "name": "Backend Dev",
    "slug": "backend-dev",
    "description": "Go backend developer",
    "role_title": "Senior Developer",
    "agent_role": "AGENT",
    "lead_mode": null,
    "status": "IDLE",
    "cli_adapter": "CLAUDE_CODE",
    "llm_provider": "ANTHROPIC",
    "llm_model": "claude-sonnet-4-20250514",
    "system_prompt": "You are a Go backend developer...",
    "avatar_seed": "backend-dev",
    "avatar_style": "bottts",
    "timeout_seconds": 1800,
    "tool_profile": "CODING",
    "memory_enabled": true,
    "cli_tools": null,
    "schedule_cron": null,
    "schedule_prompt": null,
    "schedule_enabled": false,
    "schedule_last_run": null,
    "schedule_next_run": null,
    "mcp_config_json": null,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z",
    "crew": {
      "name": "Engineering",
      "slug": "engineering",
      "color": "blue",
      "avatar_style": "bottts"
    },
    "_count": {
      "skills": 2,
      "credentials": 1,
      "chats": 5
    },
    "created_by_user_id": "user_789",
    "ephemeral": false,
    "expires_at": null,
    "expired_at": null,
    "parent_lead_id": null,
    "hire_reason": null
  }
]
mcp_config_json and created_by_user_id carry omitempty — they are dropped from the JSON entirely when null/empty rather than serialized as null.

Response Fields

FieldTypeDescription
idstringAgent ID
crew_idstringParent crew ID
workspace_idstringWorkspace ID
namestringDisplay name
slugstringURL-safe identifier
descriptionstring?Agent description
role_titlestring?Human-readable role title
agent_rolestringAGENT or LEAD
lead_modestring?"active" or "passive" (LEAD agents only)
statusstringIDLE, RUNNING, or ERROR
cli_adapterstringCLI tool used to run the agent
llm_providerstring?LLM provider name
llm_modelstring?LLM model identifier
system_promptstring?System prompt for the agent
avatar_seedstring?Seed for avatar generation
avatar_stylestring?Avatar style (e.g., "bottts", "pixel-art")
timeout_secondsintegerMaximum execution time
tool_profilestringTool access profile
memory_enabledbooleanWhether agent memory is enabled
cli_toolsstring?Comma-separated list of allowed CLI tools
schedule_cronstring?Cron expression for scheduled runs
schedule_promptstring?Prompt used for scheduled runs
schedule_enabledbooleanWhether schedule is active
schedule_last_runstring?ISO 8601 timestamp of last scheduled run
schedule_next_runstring?ISO 8601 timestamp of next scheduled run
mcp_config_jsonstring?Agent-level MCP server config (omitted when empty)
crewobject?Embedded crew info (name, slug, color, avatar_style)
_countobjectCounts: skills, credentials, chats
created_by_user_idstringCreator user ID; omitted for legacy rows with no creator
ephemeralbooleantrue for hired contractor agents, false for permanent agents
expires_atstring?RFC3339 TTL deadline (ephemeral agents only)
expired_atstring?RFC3339 ghost timestamp once the TTL elapses
parent_lead_idstring?LEAD agent that parented this ephemeral
hire_reasonstring?Timestamped hire/rehire reason log

Create Agent

POST /api/v1/agents?workspace_id={workspaceId}
Auth: OWNER, ADMIN, or MANAGER role Request Body:
FieldTypeRequiredDefaultDescription
namestringYesDisplay name (2-100 characters)
slugstringYesURL-safe identifier (2-50 chars, lowercase letters/digits/hyphens)
crew_idstringConditionalnullParent crew ID. Required when agent_role is LEAD; optional for AGENT (internal/api/agents_create.go:73)
descriptionstringNonullAgent description
role_titlestringNonullHuman-readable role title
agent_rolestringNo"AGENT"AGENT or LEAD
lead_modestringNo"active""active" or "passive" (LEAD agents only)
cli_adapterstringNo"CLAUDE_CODE"CLI adapter
llm_providerstringNonullLLM provider
llm_modelstringNonullLLM model
system_promptstringNonullSystem prompt
avatar_seedstringNonullAvatar seed
avatar_stylestringNonullAvatar style
timeout_secondsintegerNo1800Max execution time in seconds
tool_profilestringNo"CODING"Tool profile
memory_enabledbooleanNofalseEnable agent memory
{
  "name": "Backend Dev",
  "slug": "backend-dev",
  "crew_id": "crew_123",
  "agent_role": "AGENT",
  "cli_adapter": "CLAUDE_CODE",
  "system_prompt": "You are a Go backend developer...",
  "memory_enabled": true,
  "tool_profile": "CODING"
}
Agent Roles:
RoleDescriptionCrew Required
AGENTWorker that executes tasksYes
LEADCrew leader that plans and delegatesYes (max 1 per crew)
CLI Adapters: CLAUDE_CODE, CODEX_CLI, GEMINI_CLI, OPENCODE, CURSOR_CLI, FACTORY_DROID Tool Profiles: MINIMAL, CODING, FULL Response: 201 Created — returns the created agent object.
StatusCondition
400Invalid name, slug, role, or missing required crew_id
402License agent limit exceeded
403Insufficient role
409Slug already taken, or crew already has a LEAD agent

Get Agent

GET /api/v1/agents/{agentId}?workspace_id={workspaceId}
Response: 200 OK — full agent object.
StatusCondition
404Agent not found

Update Agent

PATCH /api/v1/agents/{agentId}?workspace_id={workspaceId}
Partial update — only provided fields are changed. Auth: Per-agent edit gate (canEditAgent in internal/api/agents_update.go:27, defined at internal/api/rbac.go:151). OWNER / ADMIN may edit any agent; MANAGER may edit only agents they created or agents in crews where they hold per-crew ADMIN/OWNER; MEMBER / VIEWER are refused. Request Body: Same fields as Create, all optional. status is not mutable through this endpoint. Additionally supports:
FieldTypeDescription
cli_toolsstringComma-separated CLI tools
schedule_cronstringCron expression
schedule_promptstringSchedule prompt
schedule_enabledbooleanEnable/disable schedule
mcp_config_jsonstringAgent MCP config JSON (must contain an mcpServers object)
Response: 200 OK — updated agent object.

Delete Agent

DELETE /api/v1/agents/{agentId}?workspace_id={workspaceId}
Soft-deletes the agent (sets deleted_at).
This is a destructive operation. Same per-agent edit gate as Update (canEditAgent in internal/api/agents_query.go:266, defined at internal/api/rbac.go:151). OWNER / ADMIN delete any agent; MANAGER deletes only agents they created or agents in crews where they hold per-crew ADMIN/OWNER.
Response: 200 OK
{ "success": true }

Hire / Rehire / Approve

Ephemeral agents (“contractors”) are short-lived agents spawned against a crew with a TTL. When the TTL elapses the agent becomes a ghost (the row is preserved for audit but no longer counts against the crew’s quota). The hire flow is gated by the crew’s autonomy policy (strict / guided / trusted / full) and bounded by the crew’s max_ephemeral_agents quota (ghosts excluded).
All three endpoints require OWNER, ADMIN, or MANAGER role (canRole(role, "create")).

Hire Agent

POST /api/v1/agents/hire?workspace_id={workspaceId}
Spawns a new ephemeral agent under a crew. The crew’s autonomy policy decides the outcome:
AutonomyOutcome
strict403 — the hire is rejected with a structured reason.
guided202 — the agent row is created in PENDING_REVIEW, a blocking inbox waitpoint is dropped, and the agent will not start until approved.
trusted201 — the agent goes live immediately; a non-blocking inbox visibility item is recorded.
full201 — the agent goes live immediately; journal-only, no inbox row.
Auth: OWNER, ADMIN, or MANAGER role Request Body:
FieldTypeRequiredDescription
crew_idstringConditionalTarget crew by id. Provide exactly one of crew_id / crew_slug.
crew_slugstringConditionalTarget crew by slug. Mutually exclusive with crew_id.
template_slugstringYesCrew template the ephemeral is built from (built-in or workspace-owned).
ttl_minutesintegerNoLifetime in minutes. Clamped to 1–1440; a value of 0/negative falls back to the server floor of 30.
reasonstringYesAudit/history trail for the hire.
modelstringNoLLM model override. Empty falls back to the template default at provisioning time.
parent_lead_idstringNoA LEAD agent in the same crew + workspace that parents this ephemeral.
{
  "crew_slug": "engineering",
  "template_slug": "backend-dev",
  "ttl_minutes": 60,
  "reason": "Spike: investigate the flaky deploy script",
  "model": "claude-sonnet-4-20250514"
}
Response: 201 Created (live) or 202 Accepted (pending review).
{
  "id": "agent_abc",
  "crew_id": "crew_123",
  "workspace_id": "ws_456",
  "slug": "backend-dev-eph-1a2b3c",
  "name": "Backend Dev",
  "status": "IDLE",
  "ephemeral": true,
  "expires_at": "2026-06-04T13:00:00Z",
  "expired_at": null,
  "parent_lead_id": null,
  "hire_reason": "[2026-06-04T12:00:00Z] hire: Spike: investigate the flaky deploy script",
  "pending_review": false,
  "inbox_item_id": "ib-cuid",
  "decision": "auto_log_journal"
}
FieldTypeDescription
idstringNew agent id.
crew_idstring?Resolved crew id.
workspace_idstringWorkspace id.
slugstringGenerated ephemeral slug (<template>-eph-<hex>).
namestringTemplate name.
statusstringIDLE on a live hire, PENDING_REVIEW when the policy is guided.
ephemeralbooleanAlways true.
expires_atstring?RFC3339 TTL deadline.
expired_atstring?RFC3339 ghost timestamp (always null on a fresh hire).
parent_lead_idstring?Echo of the parenting LEAD, when supplied.
hire_reasonstring?Timestamped reason log (rehires append to this).
pending_reviewbooleantrue only on the guided202 path.
inbox_item_idstringInbox row id (blocking waitpoint on guided, visibility item on trusted). Omitted when empty (full).
decisionstringThe policy decision that produced this outcome.
StatusCondition
201Live ephemeral (trusted / full).
202PENDING_REVIEW + blocking inbox item (guided).
400Missing/invalid template_slug, reason, or crew_id/crew_slug (neither, or both).
403strict autonomy rejected the hire, or caller lacks MANAGER+.
404Crew or template not found in this workspace.
429Crew quota (max_ephemeral_agents) reached — rehire a ghost or raise the quota.

Credential assignment

On a successful hire (201/202), Crewship auto-assigns the available workspace Anthropic credentials (API_KEY / AI_CLI_TOKEN, first-created wins) to the new agent so it can authenticate on first run. This is best-effort and runs after the agent row is committed: assignment failures are journaled as credential.auto_assign_failed and never fail the hire, and a workspace with no Anthropic credential journals credential.auto_assign_empty. Assign manually later via POST /api/v1/agents/{agentId}/credentials if needed.

Rehire Agent

POST /api/v1/agents/{agentId}/rehire?workspace_id={workspaceId}
Resurrects an expired ephemeral (“ghost”): clears expired_at, pushes expires_at forward by the new TTL, and appends a new reason line to the hire_reason history. The container is not rebuilt here — the chatbridge auto-provisions a fresh one on the next message. Rehiring a still-live ephemeral (extending its TTL before it ghosts) is free and does not consume an extra quota slot. Auth: OWNER, ADMIN, or MANAGER role Request Body:
FieldTypeRequiredDescription
ttl_minutesintegerNoNew lifetime in minutes. Same 1–1440 clamp / 30-minute floor as Hire.
reasonstringYesAppended to the hire_reason history trail.
{
  "ttl_minutes": 90,
  "reason": "Deploy script needs another pass"
}
Response: 200 OK — same shape as the Hire response, echoing the agent’s persisted status (rehire does not change it) and the updated expires_at / hire_reason.
StatusCondition
200Rehire succeeded; agent is live again.
400Missing reason, or invalid JSON.
403Caller lacks MANAGER+, or strict autonomy rejected the rehire.
404Agent not found in this workspace, or the agent is not ephemeral.
429Crew quota reached (only when rehiring a ghost).

Approve Hire

POST /api/v1/agents/{agentId}/approve-hire?workspace_id={workspaceId}
The guided-autonomy approval step. Flips a PENDING_REVIEW ephemeral to IDLE (an atomic conditional UPDATE, so two concurrent approvals can’t both win), resolves the blocking inbox waitpoint addressed to the agent, and writes an agent.hire_approved audit entry. The chatbridge refuses to start a PENDING_REVIEW agent, so this is what actually releases the gate. Auth: OWNER, ADMIN, or MANAGER role Request Body: empty. Response: 200 OK
{
  "id": "agent_abc",
  "status": "IDLE",
  "crew_id": "crew_123"
}
StatusCondition
200Agent flipped to IDLE; chatbridge will now serve messages.
403Caller lacks MANAGER+.
404Agent not found in this workspace.
409Agent is not in PENDING_REVIEW (already approved, hired under non-guided autonomy, or a permanent agent).

Agent Persona

The persona layer is per-agent PERSONA.md markdown that the orchestrator injects into every system prompt. Resolution is layered: the agent layer (if a file exists on disk) wins; otherwise the crew default layer; otherwise the synthesized default built from the agent’s role + role title. Direct writes are operator-only; agents edit only via the suggest endpoint, which the policy resolver gates per crew-autonomy level (see Autonomy & self-learning).

Get Persona

GET /api/v1/agents/{agentId}/persona
Returns the resolved persona — agent layer if a file is present, crew default otherwise, synthesised default if neither layer is configured. Response: 200 OK
{
  "agent_id": "agent-uuid",
  "layer": "agent",
  "from_default": false,
  "content": "# PERSONA.md...\n",
  "bytes": 1842,
  "cap_bytes": 1500
}
FieldTypeDescription
layerstringagent or crew — which layer the response came from. The synthesised default is reported as crew with from_default = true.
from_defaultbooltrue when no file exists on either layer and the synthesised “You are the …” stub was returned
cap_bytesintegerMax accepted size on PUT / POST suggest (memory.PersonaCapBytes = 1500 bytes)

Set Persona

PUT /api/v1/agents/{agentId}/persona
Content-Type: application/json
Operator-only direct write of the agent layer. Records a row in memory_versions so Get Persona History can replay the chain. Request:
{ "content": "# Engineer\n\nFocus on backend services...\n" }
Response: 200 OK
{ "layer": "agent", "bytes": 1842, "updated": "2026-05-27T18:42:11Z" }
StatusCondition
413content exceeds cap_bytes
400Invalid JSON or missing storage configuration
500Storage write/fsync failure
Agents cannot use this endpoint to mutate their own persona — ActionPersonaDirectWrite is DecisionRejected across every autonomy level in Phase 1. Agent edits flow exclusively through Suggest Persona.

Reset Persona

DELETE /api/v1/agents/{agentId}/persona
Removes the agent layer file. The next GET falls through to the crew layer (or the synthesised default).
Response: 204 No Content

Get Persona History

GET /api/v1/agents/{agentId}/persona/history?limit=20
Lists rows from memory_versions filtered to this agent’s PERSONA.md path. Pairs with GET /api/v1/admin/memory/versions/{id}/content for content drill-down — the history endpoint deliberately omits content to keep responses small. Query Parameters:
ParamDefaultMaxDescription
limit20100Page size, clamped server-side.
Response: 200 OK
{
  "entries": [
    {
      "id": "mv-cuid",
      "sha256": "deadbeef…",
      "bytes": 1842,
      "written_at": "2026-05-27T18:42:11Z",
      "written_by": "user-uuid",
      "parent_sha": "cafef00d…"
    }
  ]
}
parent_sha is omitted on the first version (when the chain starts).

Suggest Persona

POST /api/v1/agents/{agentId}/persona/suggest
Content-Type: application/json
Agent-initiated persona proposal. The crew’s autonomy policy decides the outcome:
Crew autonomyOutcome
strict / guided / trustedInbox approval item created. Agent learns pending so it can reference the proposal in subsequent runs.
fullAuto-apply + journal entry — unless the agent’s self_learning_enabled = 0, which demotes auto-apply back to inbox approval.
ActionPersonaDirectWrite is rejected across every autonomy level — this endpoint is the only path an agent can use to write a persona. Request:
{
  "content": "# Engineer\n\nFocus on backend services...\n",
  "rationale": "Operator briefly emphasised the on-call rotation; reflecting that in the persona."
}
Response: 200 OK (proposal queued or applied) / 403 (policy rejected)
{
  "agent_id": "agent-uuid",
  "decision": "inbox_approve",
  "bytes": 1842,
  "rationale": "...",
  "timestamp": "2026-05-27T18:42:11Z",
  "applied": false,
  "pending": true
}
FieldTypeDescription
decisionstringPolicy decision that drove the outcome (e.g. inbox_approve, auto_journal, rejected).
appliedbooltrue only when the suggestion was auto-applied to disk (full autonomy + self_learning_enabled = 1).
pendingbooltrue when the proposal was queued for operator approval (present on the inbox path).
self_learning_gatestring?"off" only when a per-agent self_learning_enabled = 0 demoted an auto-apply decision back to inbox approval.
There is no inbox_id in this response — the proposal is recorded as an audit_logs row (persona.suggest_pending), not returned to the caller.
StatusCondition
400Missing / empty content field
403Policy rejected the suggestion (autonomy floor)
413content exceeds cap_bytes
503Server built without storage configuration (outputBasePath empty)

Agent Self-Learning Posture

Per-agent flag controlling whether ALLOW decisions from the Keeper evaluators auto-apply (enabled = true) or queue for operator approval in the inbox (enabled = false). Orthogonal to the crew’s autonomy_level: the per-action policy gate (policy.DecideAction) is the upstream authority — this flag only decides what happens to already-ALLOWed decisions. See Autonomy & self-learning.

Get Self-Learning

GET /api/v1/agents/{agentId}/learning
Returns the current flag plus the audit triple (who flipped it, when, why). Any authenticated workspace member can read — the value is non-secret diagnostic state. Response: 200 OK
{
  "agent_id": "agent-uuid",
  "enabled": true,
  "set_by_user_id": "user-uuid",
  "set_at": "2026-05-26T09:14:22Z",
  "reason": "Trusted crew, agent has cleared 50+ approvals without an override"
}
FieldTypeNotes
enabledboolPersisted as 0 / 1 on agents.self_learning_enabled.
set_by_user_idstring?Omitted on agents that have never had the flag flipped.
set_atstring?RFC3339; omitted when never set.
reasonstring?Operator-supplied rationale from the most recent PATCH; omitted when never set.

Set Self-Learning

PATCH /api/v1/agents/{agentId}/learning
Content-Type: application/json
Flip the flag. Requires OWNER / ADMIN (canRole(role, "manage")) — self-learning weakens the inbox-approval invariant, so the operator who turns it on must be senior enough to own the consequences. Request:
{
  "enabled": true,
  "reason": "Trusted crew, agent has cleared 50+ approvals without an override"
}
FieldTypeRequiredNotes
enabledboolyesDecoded as *bool server-side — a missing or null field returns 400, not a silent flip to false. Be explicit about direction.
reasonstringyesNon-empty after trim. Persisted on the row so a later audit can answer “who turned this on, when, and why”.
Response: 200 OK — same shape as GET, reflecting the new state and the freshly-recorded audit triple.
StatusCondition
400enabled field missing / null, or reason empty
403Caller is not OWNER / ADMIN
404Agent not found in the current workspace

Agent Inbox Summary

GET /api/v1/agents/{agentId}/inbox
Consolidated “what’s waiting on this agent” payload used by the Crews preview panel — one round-trip instead of four parallel fetches across approvals, assignments, escalations, and peer messages. UI is the primary consumer; operators can poll the endpoint for an at-a-glance load check. Response: 200 OK
{
  "approvals_pending": 2,
  "assignments_open": 5,
  "escalations_open": 0,
  "peer_messages": [
    {
      "id": "pm-uuid",
      "from_agent_name": "Martin",
      "from_agent_slug": "martin",
      "question": "What's the latest on the deploy script?",
      "response": null,
      "escalated": false,
      "status": "open",
      "created_at": "2026-05-27T12:30:00Z",
      "direction": "incoming"
    }
  ],
  "cost_usd_this_month": 4.21,
  "llm_calls_this_month": 312,
  "tokens_used_this_month": 184221
}
FieldTypeDescription
approvals_pendingintInbox waitpoint items awaiting this agent’s decision.
assignments_openintTasks routed to this agent that have not reached a terminal state.
escalations_openintPeer conversations escalated up the lead chain that still target this agent.
peer_messagesarrayLast N peer messages (both directions). direction is "incoming" or "outgoing"; response and duration_ms are populated only for resolved exchanges.
cost_usd_this_monthfloatSum of cost_ledger.cost_usd for this agent in the current calendar month. 0 when the cost_ledger table is absent (older workspaces).
llm_calls_this_monthintCount of cost_ledger rows for this agent in the current month.
tokens_used_this_monthint64Sum of input_tokens + output_tokens from cost_ledger in the current month.
StatusCondition
401Workspace context missing
404Agent not found in the current workspace

Agent Peer Cards

Per-(agent, user) markdown notes produced by the PeerCardSync routine — the agent’s own “what I know about this person” file. These endpoints are operator-facing: the cards live on disk under {outputBase}/crews/{crewID}/agents/{slug}/.memory/peers/, and every read/write/delete writes a peer_card_audit row for GDPR SAR coverage. The user-facing surface (view-mine / opt-out / delete-mine) lives under /api/v1/users/me/peer-cards and shares the same disk + DB primitives — these agent-flavor endpoints exist so an operator can clean up cards on behalf of a user (compliance ticket, leaver workflow) or inspect what an agent has been writing about people. The {userId} path parameter is the raw user_id; the server derives the user_slug via memory.UserSlug so the URL stays debuggable.

List Agent Peers

GET /api/v1/agents/{agentId}/peers
Returns the agent’s peer-card index (no card content — fetch via the single-card endpoint below). Workspace-scoped; agents in other workspaces return 404. Response: 200 OK
{
  "agent_id": "agent-uuid",
  "peers": [
    {
      "id": "pc-uuid",
      "user_id": "user-uuid",
      "user_slug": "alice",
      "bytes": 412,
      "created_at": "2026-05-10T...",
      "updated_at": "2026-05-26T..."
    }
  ]
}
StatusCondition
404Agent not found in the current workspace
409Agent has no crew_id — peer cards require a crew-scoped path

Get Agent Peer Card

GET /api/v1/agents/{agentId}/peers/{userId}
Returns one card’s full content (markdown). Writes a read row to peer_card_audit. Response: 200 OK
{
  "agent_id": "agent-uuid",
  "user_id": "user-uuid",
  "user_slug": "alice",
  "content": "# Alice Chen\n\n- Prefers async over sync...\n",
  "bytes": 412
}
StatusCondition
404Agent not found, or no card exists for this (agent, user)
409Agent has no crew_id
503outputBasePath not configured server-side

Delete Agent Peer Card

DELETE /api/v1/agents/{agentId}/peers/{userId}
Removes the card from disk and the peer_cards row, then writes a delete audit row. Idempotent — deleting a non-existent card returns 204 rather than 404 so an operator script can retry safely.
Response: 204 No Content
The GDPR cascade endpoint DELETE /api/v1/admin/users/{userId}/data deletes peer cards across all agents in the workspace for a single user — that’s the right path for a full SAR. This per-agent endpoint is for the narrower “clean up just this agent’s notes about this user” case.

Agent Skills

Assign, list, and remove the skills available to an agent.

List Skills

GET /api/v1/agents/{agentId}/skills?workspace_id={workspaceId}
Response: 200 OK — array of skill assignment objects.

Add Skill

POST /api/v1/agents/{agentId}/skills?workspace_id={workspaceId}
Auth: OWNER, ADMIN, or MANAGER role (canRole(role, "create")) Request Body:
FieldTypeRequiredDescription
skill_idstringYesSkill to assign
configstringNoPer-assignment config blob (stored as-is)
{
  "skill_id": "skill_abc"
}
Response: 201 Created{ "id": "<assignment_id>" } on a fresh assign. The assign is idempotent: re-assigning an already-installed skill returns 200 OK with { "id": "<existing_id>", "already_assigned": true } rather than a 409.

Remove Skill

DELETE /api/v1/agents/{agentId}/skills/{skillId}?workspace_id={workspaceId}
Response: 204 No Content

Agent Credentials

Assign, list, and remove the credentials injected into an agent’s container.

List Credentials

GET /api/v1/agents/{agentId}/credentials?workspace_id={workspaceId}
Response: 200 OK — array of credential assignment objects.

Assign Credential

POST /api/v1/agents/{agentId}/credentials?workspace_id={workspaceId}
Auth: OWNER or ADMIN role (canRole(role, "manage") in internal/api/agent_credentials.go:89) Request Body:
FieldTypeRequiredDescription
credential_idstringYesCredential to assign (must exist in this workspace)
env_var_namestringYesEnvironment variable name to inject into the agent container
priorityintegerNoResolution priority when multiple credentials share an env_var_name
{
  "credential_id": "cred_abc",
  "env_var_name": "ANTHROPIC_API_KEY",
  "priority": 0
}
Response: 201 Created
{ "id": "<assignment_id>" }
StatusCondition
400Missing credential_id or env_var_name
403Insufficient role
404Agent or credential not found
409Credential already assigned to this agent

Remove Credential

DELETE /api/v1/agents/{agentId}/credentials/{assignmentId}?workspace_id={workspaceId}
The path segment is the assignment ID (agent_credentials.id), not the credential id. Auth: OWNER or ADMIN role (canRole(role, "manage") in internal/api/agent_credentials.go:146) Response: 200 OK
{ "success": true }

Agent Chats & Runs

List and create interactive chat sessions, and review execution history.

List Chats

GET /api/v1/agents/{agentId}/chats?workspace_id={workspaceId}
Response: 200 OK — array of chat session objects.

Create Chat

POST /api/v1/agents/{agentId}/chats?workspace_id={workspaceId}
Creates a new interactive chat session with the agent. Response: 201 Created

Agent Runs

GET /api/v1/agents/{agentId}/runs?workspace_id={workspaceId}
List execution history for an agent. Response: 200 OK — array of run objects.

Agent Proxy Endpoints

These endpoints proxy requests through the crewshipd sidecar to the agent’s container.
POST /api/v1/agents/{agentId}/stop halts a running agent.
EndpointMethodDescription
GET /api/v1/agents/{agentId}/debugGETDebug information for a running agent
GET /api/v1/agents/{agentId}/filesGETList files in agent workspace
GET /api/v1/agents/{agentId}/files/download?path=...GETDownload a file
PUT /api/v1/agents/{agentId}/files/savePUTSave/upload a file
GET /api/v1/agents/{agentId}/container-filesGETList files inside the running container (see below)
GET /api/v1/agents/{agentId}/git-logGETRecent git commits from the container workspace (see below)
GET /api/v1/agents/{agentId}/logsGETGet agent logs
POST /api/v1/agents/{agentId}/stopPOSTStop a running agent

Container Files

GET /api/v1/agents/{agentId}/container-files?subdir={path}
Lists files inside the agent’s running container (as opposed to /files, which lists the persistent workspace mount). Proxied through crewshipd; the request is rejected before the IPC hop if the agent is not assigned to a crew. (internal/api/proxy_files.go:350) Auth: Session or CLI token + workspace membership with at least read-tier role (canRole(role, "read")) Query Parameters:
ParameterTypeDescription
subdirstringOptional sub-path within the container’s working directory (normalized to reject .. traversal)
Response: 200 OK — JSON array of file entries (unwrapped from the IPC layer’s {files: [...]} envelope). Returns [] when the sidecar response is unparseable or empty.
StatusCondition
400subdir path is invalid (traversal or absolute)
403Caller lacks read-tier role
404Agent not found, or agent not assigned to a crew
502Sidecar IPC call failed

Git Log

GET /api/v1/agents/{agentId}/git-log
Fetches recent git commits from the agent’s container workspace. Proxied through crewshipd; the agent’s slug is forwarded as agent_slug so the sidecar can scope the log to that agent’s directory inside the shared crew container. (internal/api/proxy.go:312) Auth: Session or CLI token + workspace membership with at least read-tier role (canRole(role, "read")) Response: 200 OK — JSON array of commit entries (unwrapped from the IPC layer’s {commits: [...]} envelope). Returns [] when the sidecar response is unparseable or empty.
StatusCondition
403Caller lacks read-tier role
404Agent not found, or agent not assigned to a crew
502Sidecar IPC call failed

Chat Messages

GET /api/v1/chats/{chatId}/messages
Get messages for a specific chat session. Proxied through crewshipd. Supports ?offset= and ?limit= (default 50, max 500). Auth: Session or CLI token with read-tier role, plus membership of the chat’s workspace. The workspace is resolved from the chat row (no workspace_id query param needed); the caller must appear in workspace_members for that workspace or the call returns 403. (internal/api/proxy.go:258) Response: 200 OK — proxied message payload. A chat that doesn’t exist yet (new session before its first message) returns { "messages": [] } rather than 404.
StatusCondition
403Caller lacks read-tier role, or is not a member of the chat’s workspace
502Sidecar IPC call failed