Open API
The PryFox AI Open API lets external systems create tasks, query tasks and logs, listen to real-time task events, confirm plan-mode tasks, and manage workspaces using a project access key. Tasks are isolated by workspace — multiple calls within the same workspace share the same agent instance, memory, and conversation history.
Basics
- Base URL:
https://pryfox.ai/openapi(the Open API is a top-level/openapinamespace, separate from the/apiprefix used by internal endpoints) - All endpoints return a unified envelope:
{ code, message, data }code === 0means success; non-zero means an errormessageis a human-readable descriptiondataholds the business payload
Authentication
The Open API uses a project access key to exchange for a JWT Token. Subsequent requests are authenticated with a Bearer token.
1. Exchange for a token
POST /openapi/auth/project-key
Content-Type: application/json
{
"key": "proj_<uuid>_<random>",
"projectUuid": "optional project UUID for validation"
}Success response:
{
"code": 0,
"message": "认证成功",
"data": {
"token": "eyJhbG...",
"csrfToken": "a49e9e4b88a34ce0b2d63bfb53a885ad",
"project": { "uuid": "...", "name": "..." },
"permissions": { "tasks": ["read", "write"] }
}
}The response also sets project_token and csrfToken cookies via the Set-Cookie header. Pure backend callers (no cookie jar) can use the token from the body as Authorization: Bearer and the csrfToken as the X-CSRF-Token header directly.
2. Calling endpoints
| Scenario | Requirements |
|---|---|
| Read endpoints (GET) | Authorization: Bearer {token} |
| Write endpoints (POST/PUT/DELETE) | Authorization: Bearer {token} + X-CSRF-Token: {csrfToken} |
| SSE streaming | Authorization: Bearer {token}, or append ?token={token} to the URL because EventSource cannot set custom headers |
The project-key permission field permissions.tasks includes:
read: query tasks, logs, plans, streams, and workspaceswrite: create tasks, manage workspaces, confirm / reject plans
Confirming / rejecting a plan only requires tasks:write; there is no separate approve permission.
Workspaces and channels
The workspace is the core isolation unit of the Open API. Every task belongs to a workspace, specified by the caller via workspace_id (UUID) when creating the task.
task_id: the public task ID used to query / stream / confirm plansworkspace_id: the workspace UUID, held by the caller, that determines the task's runtime ownershipexternal_user_id: the internal runtime user key used by Claw — equalsworkspace_id. All tasks within the same workspace share the same agent instance, memory store, and continuous conversation history; the second call with the sameworkspace_idrecalls the context of the first. Data from different workspaces is never visible to each other.
Channels determine where a task is delivered within a workspace. The Open API exposes two logical channels:
| Logical channel | Meaning | Full channel (server-composed) | Dispatch |
|---|---|---|---|
work-group | Workspace group chat | workspace:{workspace_id} | Coordinator auto-selects an agent |
agent:{agent_id} | Private chat with an agent | agent:{agent_id}:ws:{workspace_id} | Manually assigns that agent |
The caller passes the logical channel; the server composes the full channel using the same encoding the web UI uses — so Open-API tasks appear in the same channel as the web UI for that workspace. Do not pass the composed full channel, and do not include the :ws:{workspace_id} suffix — the server appends it from workspace_id.
Workspaces are persistent entities:
- Implicitly created on first task dispatch (
nameis empty; the frontend list falls back to a short UUID), or explicitly created and named via the workspace management endpoints. - After a soft delete, dispatching another task with the same
workspace_idautomatically revives it asactive— so "delete" is effectively "pause" for third parties; the held ID never deadlocks. - All authorized identities in the project can see and intervene in the same workspace.
Endpoints
Create a task
POST /openapi/projects/{project_id}/tasksDispatch a task. The task is bound to the workspace_id provided by the caller, and routed through a logical channel the caller also supplies.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Task content |
workspace_id | string (UUID) | Yes | Workspace ID; determines runtime ownership (same ID shares agent instance/memory/history) |
channel | string | Yes | Logical channel: work-group (workspace group chat) or agent:{agent_id} (private chat with an agent). Server composes the full channel from this + workspace_id. |
mode | string | No | Three-state: plan (force plan-confirmation) / auto (force auto-execute) / omitted (fall back to workspace → project default, see below). |
metadata | object | No | Custom metadata stored with the message |
reply_to | string | No | Only valid for the work-group channel; the message ID being replied to |
Plan-mode resolution priority (when mode is omitted, the task's force_plan_mode is computed by this fallback):
- Explicit
mode='plan'→true;mode='auto'→false - Workspace default
workspaces.settings.plan_mode_enabled(set by project members in the web UI workspace management page) - Project default
projects.settings.plan_mode_enabled(fallback when the workspace is unset) - Fallback
false
The Open API workspace management endpoints do not expose
plan_mode_enabled— the workspace-level plan-mode default is set by project members in the web UI. To control plan mode per task, the third party can passplanorautoin themodefield of the create-task call.
Response:
{
"code": 0,
"message": "任务已派发",
"data": {
"task_id": "42987a4f-e35b-43e2-bd81-c8a527bdcaba",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"status": "pending",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"logical_channel": "work-group",
"agent_id": null,
"mode": "plan",
"plan_mode_enabled": true,
"message_db_id": "0c6e0d89-dc03-4750-bdc3-2745f8cf966e",
"created_at": "2026-07-07T11:32:58.416Z"
}
}channel: the composed full channel (work-group→workspace:{workspace_id};agent:{agent_id}→agent:{agent_id}:ws:{workspace_id}).logical_channel: the logical channel the caller passed (work-group/agent:{agent_id}).agent_id:nullfor awork-groupgroup chat (coordinator auto-selects); foragent:{agent_id}private chat, the agent ID.mode: echoes the caller's logical value (plan/auto; omitted is stored asauto).plan_mode_enabled: the effective plan-mode boolean for this task (explicit > workspace default > project fallback), so the caller knows whether plan confirmation follows.- If
workspace_iddoes not correspond to an existing workspace, a workspace row is implicitly created; if it was previously soft-deleted, it is automatically revived toactive.
List tasks
GET /openapi/projects/{project_id}/tasks?workspace_id={uuid}&status=running&page=1&limit=20List tasks filtered by workspace / status, with pagination. Supports page/limit pagination or before cursor pagination.
Query parameters:
| Param | Type | Description |
|---|---|---|
workspace_id | string (UUID) | Optional; when provided, only lists tasks under this workspace |
status | string | Optional: pending / running / planning / completed / failed / cancelled |
page | number | Page number, default 1 (mutually exclusive with before; ignored when before is set) |
limit | number | Page size, default 20, max 100 |
before | string (ISO timestamp) | Optional; cursor pagination, returns tasks with created_at < before |
Response:
{
"code": 0,
"message": "获取任务列表成功",
"data": {
"tasks": [
{
"task_id": "42987a4f-...",
"workspace_id": "8e6bc849-...",
"channel": "workspace:8e6bc849-...",
"agent_id": null,
"mode": "plan",
"status": "completed",
"content": "...",
"plan_id": "492c1f7a-...",
"started_at": "2026-07-07T03:33:01.528Z",
"completed_at": "2026-07-07T03:33:01.575Z",
"result_summary": "...",
"metadata": {},
"created_at": "2026-07-07T11:32:58.416Z",
"updated_at": "2026-07-07T11:33:01.575Z"
}
],
"has_more": false,
"next_before": null,
"page": 1,
"limit": 20
}
}Key ownership: the list only returns tasks created by the current key.
next_beforeis the cursor — pass it asbeforeon the next request to fetch older pages;nullmeans you've reached the earliest.
Get task status
GET /openapi/projects/{project_id}/tasks/{task_id}Response:
{
"code": 0,
"message": "获取任务状态成功",
"data": {
"task_id": "42987a4f-e35b-43e2-bd81-c8a527bdcaba",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"status": "completed",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"mode": "plan",
"content": "Full lifecycle test",
"agent_id": null,
"plan_id": "492c1f7a-f20c-4c56-8293-c384b1530a44",
"started_at": "2026-07-07T03:33:01.528Z",
"completed_at": "2026-07-07T03:33:01.575Z",
"result_summary": "...",
"metadata": {},
"created_at": "2026-07-07T11:32:58.416Z",
"updated_at": "2026-07-07T11:33:01.575Z",
"active_run": null
}
}State machine: pending → planning / running → completed | failed | cancelled.
active_run: best-effort field; returns{ agent_id, tool_name, started_at, updated_at }when the task has an in-flight execution, otherwisenull.- Key ownership: only tasks created by the current key may be read; otherwise 403 "任务不属于当前密钥".
Get channel logs
GET /openapi/projects/{project_id}/channels/logs?channel=work-group&workspace_id={uuid}&limit=50&before=2026-07-07T11:33:00ZChannel-level logs: pass a logical channel + workspace_id as dual parameters; the server composes the full channel and queries the conversation logs for that channel in that workspace — events from all tasks in a channel are aggregated into one continuous conversation flow, so there's no need to query per task_id. This mirrors the web UI's same-channel log view.
Pass the logical channel (
work-grouporagent:{agent_id}), not the composed full channel. Do not include the:ws:{workspace_id}suffix — the server appends it fromworkspace_idautomatically.
Channel dispatch (mirrors the web UI's two log-query modes):
work-group: the workspace group chat. Aggregates the workspace'sagent_logs(message/tool_call/tool_response/progress/error/llm_request/llm_response) and user questions (work_group_messageswheresender_type='user').agent:{agent_id}: a private chat with an agent in the workspace. Aggregates that agent'sagent_logsin that workspace and the private-chat history (agent_private_messages, including user-question dividers).
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
channel | string | yes | Logical channel: work-group or agent:{agent_id} |
workspace_id | string (UUID) | yes | Workspace ID |
limit | number | no | Max number of entries, default 50, max 100 |
before | string (ISO timestamp) | no | Cursor pagination, return entries before this timestamp |
Response:
{
"code": 0,
"message": "获取频道日志成功",
"data": {
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"logical_channel": "work-group",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"logs": [
{
"agent_id": null,
"agent_name": null,
"agent_title": null,
"agent_avatar": null,
"type": "message",
"data": "Create a plan for me",
"error_message": null,
"metadata": {},
"sender_type": "user",
"timestamp": "2026-07-07T11:32:58.416Z"
},
{
"agent_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"agent_name": "PlanTester",
"agent_title": "Plan Tester",
"agent_avatar": null,
"type": "llm_response",
"data": "...",
"error_message": null,
"metadata": {},
"sender_type": null,
"timestamp": "2026-07-07T11:32:59.000Z"
}
],
"has_more": false,
"next_before": null
}
}- Possible
typevalues:message,llm_request,llm_response,tool_call,tool_response,progress,error, etc. sender_type:userfor a user question (conversation divider),nullfor an agent event.next_before: cursor — pass it asbeforeon the next request to fetch older logs;nullmeans you've reached the earliest.
Real-time event stream (SSE)
GET /openapi/projects/{project_id}/channels/stream?channel=work-group&workspace_id={uuid}&token={token}
Accept: text/event-streamChannel-level SSE: subscribe to one full channel (composed from channel + workspace_id) and receive status changes, logs, pending plans, and completion/failure events for all tasks in that channel in real time — when a channel has multiple tasks, all events are pushed over the same connection and the client distinguishes each task by data.task_id.
Channel-level subscriptions are gated by project membership (a project key already gates project membership). The same channel may receive tasks dispatched by multiple keys within the same project, and the channel view is visible to all same-project keys (mirroring the web UI's project-wide visibility).
Channel-level is a long-lived subscription; the channel may keep receiving new tasks, so the connection stays open until the client disconnects.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
channel | string | yes | Logical channel: work-group or agent:{agent_id} |
workspace_id | string (UUID) | yes | Workspace ID |
token | string | yes* | Access token. Passed via URL since EventSource cannot set custom headers; not needed in the URL when you can use the Authorization: Bearer header |
SSE events:
| Event | Description |
|---|---|
connected | Connection established, includes the channel's current active-task snapshot (active_tasks: pending/running/planning) |
status_changed | Task status changed (one is emitted per active task on connect to sync the current state) |
log | A new log was produced; data.task_id identifies the task, data.type is the log type |
plan_pending | A new plan is pending confirmation; data.task_id identifies the task |
completed | Task completed |
failed | Task failed |
cancelled | Task cancelled |
heartbeat | Heartbeat every 30 seconds |
error | Initialization failed (auth / parameter error) |
Each event's data carries a task_id (except connected/heartbeat/error), letting the client bucket per-task events within the same channel. The connected event's data contains an active_tasks array, each item {task_id, status, mode, plan_id, started_at}.
Typical usage: track a single task's progress and disconnect on completion
Channel-level is a long-lived subscription. The typical flow is "dispatch a task → subscribe to that workspace's channel → filter by task_id to watch only your task → close() on a terminal event (completed/failed/cancelled)". EventSource auto-reconnects on disconnect — no extra handling needed.
// After dispatching a task to workspace A's work-group, track it and disconnect on completion
const TASK_ID = '42987a4f-e35b-43e2-bd81-c8a527bdcaba'; // from the POST /tasks response
const PROJECT = 'your-project-uuid';
const WORKSPACE = 'workspace-A-uuid';
const TOKEN = 'eyJhbG...';
const url = `https://pryfox.ai/openapi/projects/${PROJECT}/channels/stream`
+ `?channel=work-group&workspace_id=${WORKSPACE}&token=${encodeURIComponent(TOKEN)}`;
const es = new EventSource(url);
es.addEventListener('connected', e => {
const snap = JSON.parse(e.data);
console.log('Connected, channel active tasks:', snap.active_tasks);
});
es.addEventListener('log', e => {
const d = JSON.parse(e.data);
if (d.task_id !== TASK_ID) return; // watch only our task
console.log(`[${d.type}] ${d.data ?? ''}`);
});
es.addEventListener('status_changed', e => {
const d = JSON.parse(e.data);
if (d.task_id === TASK_ID) console.log('Status:', d.status);
});
// Plan-mode task: on plan_pending, call POST /plans/{plan_id}/confirm to approve
es.addEventListener('plan_pending', e => {
const d = JSON.parse(e.data);
if (d.task_id === TASK_ID) console.log('Plan pending:', d.plan_id);
});
// Close the connection when the task reaches a terminal state
const closeOnTerminal = e => {
const d = JSON.parse(e.data);
if (d.task_id === TASK_ID) { console.log('Task ended:', d.status); es.close(); }
};
['completed', 'failed', 'cancelled'].forEach(t => es.addEventListener(t, closeOnTerminal));List pending plans
GET /openapi/projects/{project_id}/plans/pending?channel=work-group&workspace_id={uuid}Returns pending plans (status = proposed) by channel. Pass a logical channel + workspace_id as dual parameters; the server composes the full channel and filters directly on agent_plans.channel — only pending plans for that channel in that workspace are returned.
Pass the logical channel (
work-grouporagent:{agent_id}), not the composed full channel. Do not include the:ws:{workspace_id}suffix.Examples:
?channel=work-group&workspace_id=Areturns pending plans for workspace A's group chat;?channel=agent:agent1&workspace_id=Areturns pending plans for the private chat with agent1 in workspace A.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
channel | string | yes | Logical channel: work-group or agent:{agent_id} |
workspace_id | string (UUID) | yes | Workspace ID |
Response:
{
"code": 0,
"message": "获取待确认计划成功",
"data": {
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"logical_channel": "work-group",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"plans": [
{
"plan_id": "492c1f7a-f20c-4c56-8293-c384b1530a44",
"agent_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"title": "Mock Plan: Verify Open API Flow",
"description": "...",
"status": "proposed",
"session_id": "1e523e38-24fe-42cd-9eb1-76fd03f18c17",
"task_id": "42987a4f-e35b-43e2-bd81-c8a527bdcaba",
"created_at": "2026-07-07T11:33:00.000Z",
"items": [
{
"id": "...",
"step_number": 1,
"title": "Confirm endpoint works",
"description": "...",
"expected_skill": "",
"status": "pending"
}
]
}
]
}
}Each plan's
channelis the full channel written by storeAgentPlan (workspace:{wid}for group chat /agent:{aid}:ws:{wid}for private chat), matching the composed query channel.agent_idis the agent that produced the plan. Whenchannelisagent:{agent_id}, that agent must belong to the current project.
Confirm / reject a plan
POST /openapi/projects/{project_id}/plans/{plan_id}/confirmRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
confirm | boolean | yes | true to approve and execute, false to reject |
feedback | string | required on reject | Rejection reason (required when confirm: false) |
workspace_id | string (UUID) | yes | The workspace the plan belongs to; must match the workspace parsed from the plan's channel (plan.channel) |
{
"confirm": true,
"feedback": "",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282"
}Workspace ownership check:
workspace_idis required and must match the workspace parsed from the plan's channel; otherwise a 403 "计划不属于当前工作空间" is returned.Key ownership check: when called with a project key, only plans associated with a task created by the current key may be confirmed. If the plan's task was created by another key, a 403 "计划不属于当前密钥" is returned. Tasks with no
key_idare allowed.
Response:
{
"code": 0,
"message": "计划已确认",
"data": {
"plan_id": "492c1f7a-f20c-4c56-8293-c384b1530a44",
"task_id": "42987a4f-e35b-43e2-bd81-c8a527bdcaba",
"status": "confirmed",
"agent_id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282",
"workspace_id": "8e6bc849-b634-4a85-aa68-594a301dd282"
}
}
status:confirm: truereturnsconfirmed(task transitions torunning);confirm: falsereturnsrejected(task transitions tocancelled, with the rejection reason recorded inresult_summary). A timed-out session returns 410.
Workspace management
Third parties can explicitly create, name, archive, and delete workspaces. All workspace endpoints require the project key to include tasks:read (read) or tasks:write (write).
List workspaces
GET /openapi/projects/{project_id}/workspacesReturns all active + archived workspaces in the project (soft-deleted ones are excluded). Each item includes the task count and whether it has an active task.
Response:
{
"code": 0,
"message": "获取工作空间列表成功",
"data": {
"workspaces": [
{
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"name": "Support pool",
"description": "Workspace for end-user inquiries",
"status": "active",
"created_by": 12,
"created_at": "2026-07-07T11:32:58.416Z",
"updated_at": "2026-07-07T11:32:58.416Z",
"task_count": 5,
"has_active_task": false,
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282"
}
]
}
}Create a workspace
POST /openapi/projects/{project_id}/workspacesExplicitly creates a workspace with an optional name. The returned id can be reused as the workspace_id when creating tasks later.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Workspace name, max 100 chars; when omitted, the frontend list falls back to a short UUID |
description | string | No | Workspace description |
Response:
{
"code": 0,
"message": "工作空间已创建",
"data": {
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"name": "Support pool",
"description": "Workspace for end-user inquiries",
"status": "active",
"created_by": 12,
"created_at": "2026-07-07T11:32:58.416Z",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282"
}
}Get workspace detail
GET /openapi/projects/{project_id}/workspaces/{workspace_id}Response:
{
"code": 0,
"message": "获取工作空间详情成功",
"data": {
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"name": "Support pool",
"description": "Workspace for end-user inquiries",
"status": "active",
"created_by": 12,
"created_at": "2026-07-07T11:32:58.416Z",
"updated_at": "2026-07-07T11:32:58.416Z",
"task_count": 5,
"active_task_count": 0,
"message_count": 23,
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282"
}
}task_count: total number of tasks under this workspace (including history).active_task_count: number of active tasks inpending/running/planning.message_count: number of group-chat messages in this workspace.
Update a workspace
PUT /openapi/projects/{project_id}/workspaces/{workspace_id}Renames, changes the description, or toggles status between active ↔ archived. Archiving is reversible and does not delete history. At least one updatable field must be provided.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New name, max 100 chars; an empty string clears it to null |
description | string | No | New description |
status | string | No | active or archived |
Response:
{
"code": 0,
"message": "工作空间已更新",
"data": {
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"name": "Support pool v2",
"description": "Workspace for end-user inquiries",
"status": "archived",
"created_by": 12,
"created_at": "2026-07-07T11:32:58.416Z",
"updated_at": "2026-07-08T09:00:00.000Z",
"channel": "workspace:8e6bc849-b634-4a85-aa68-594a301dd282"
}
}The workspace-level plan-mode default (
plan_mode_enabled) is set by project members in the web UI workspace management page and is not exposed through the Open API workspace endpoints. To control plan mode per task, the third party can passplanorautoin themodefield of the create-task call.
Delete a workspace
DELETE /openapi/projects/{project_id}/workspaces/{workspace_id}Soft-deletes the workspace (status = deleted + deleted_at), retaining historical messages / logs / task rows for auditing.
Deleting with active tasks: if the workspace has active tasks (pending / running / planning), deletion is still allowed (soft-delete). The program terminates these tasks: in-progress streaming execution is aborted (a cancel_message is sent to Claw) and the task records are set to cancelled (terminal state); task rows are retained as history for auditing. The has_active_task field returned by the list endpoint can be used to surface a warning in the frontend.
Revive semantics: after a soft delete, dispatching another task with the same
workspace_idautomatically revives it toactive. So for Open API third parties, "delete" is effectively "pause" — the held workspace ID never deadlocks due to a management-side soft delete.
Response:
{
"code": 0,
"message": "工作空间已删除",
"data": {
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"terminated_tasks": 2
}
}
terminated_tasks: the number of active tasks that were terminated on deletion. Conversation data under this workspace (group/private messages soft-deleted; logs/plans hard-deleted) and Claw memory data are also purged; main-channel data (workspace_id IS NULL) is unaffected. A revived workspace starts with a blank context — previous conversations are not retained.
Agent list (internal endpoint)
GET /api/projects/{project_id}/agents?page=1&limit=20&status=active&search=PlanAgents within a workspace are auto-selected by the coordinator; you do not need to specify an agent_id when creating a task. If you want to know which agents exist in the project and their configuration, use this endpoint. Note this endpoint uses the internal /api/ prefix, not /openapi/.
Permission required: the project key must include
employees:read.
Query parameters:
page: page number, default 1limit: page size, default 20, max 100status: filter by statussearch: fuzzy search by name or title
Response:
{
"code": 0,
"message": "获取智能体列表成功",
"data": {
"agents": [
{
"id": "8e6bc849-b634-4a85-aa68-594a301dd282",
"name": "PlanTester",
"title": "Plan Tester",
"avatar": null,
"role": "member",
"is_system": false,
"model": { "id": "...", "name": "mock-llm" },
"status": "idle",
"status_text": "等待任务",
"device": null,
"self_reflect": false,
"plan_mode_enabled": true,
"stats": {},
"skill_count": 0,
"created_at": "2026-07-06T...",
"updated_at": "2026-07-06T..."
}
],
"user_role": "member",
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1,
"has_more": false
}
}
}An agent's
plan_mode_enabledis that agent's own plan-mode toggle (configured when the agent is created) — a distinct concept from the workspace-level plan-mode default.statusis affected by the current user's in-flight tasks (showsworkingwhen there are in-flight tasks).
Common error codes
| HTTP status | Description |
|---|---|
| 400 | Bad request, e.g. empty content, invalid workspace_id, missing feedback when rejecting, invalid status value |
| 401 | Not authenticated or token expired |
| 403 | No project permission, key lacks the required action permission, or task/plan does not belong to the current key/workspace |
| 404 | Task, agent, plan, or workspace not found |
| 409 | Plan already processed |
| 410 | Plan confirmation session timed out |
| 429 | Authentication endpoint rate limited |
| 500 | Server internal error |
Example
# 1. Exchange for a token
TOKEN_RESP=$(curl -s -X POST https://pryfox.ai/openapi/auth/project-key \
-H 'Content-Type: application/json' \
-d '{"key":"proj_xxxxx_xxxxxx"}')
TOKEN=$(echo "$TOKEN_RESP" | jq -r '.data.token')
CSRF=$(echo "$TOKEN_RESP" | jq -r '.data.csrfToken')
PROJECT='your-project-uuid'
WORKSPACE='your-workspace-uuid' # optionally create via the workspace API, or let the first task implicitly create it
# 2. Create a workspace (optional, for named management)
curl -s -X POST "https://pryfox.ai/openapi/projects/$PROJECT/workspaces" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-H "X-CSRF-Token: $CSRF" \
-d '{"name":"Support pool","description":"Workspace for end-user inquiries"}'
# 3. Create a task (workspace-scoped, group channel + plan mode)
TASK_RESP=$(curl -s -X POST "https://pryfox.ai/openapi/projects/$PROJECT/tasks" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-H "X-CSRF-Token: $CSRF" \
-d "{\"content\":\"Create a plan for me\",\"workspace_id\":\"$WORKSPACE\",\"channel\":\"work-group\",\"mode\":\"plan\"}")
TASK_ID=$(echo "$TASK_RESP" | jq -r '.data.task_id')
# 4. List pending plans for this workspace's work-group channel
PENDING=$(curl -s "https://pryfox.ai/openapi/projects/$PROJECT/plans/pending?channel=work-group&workspace_id=$WORKSPACE" \
-H "Authorization: Bearer $TOKEN")
PLAN_ID=$(echo "$PENDING" | jq -r '.data.plans[0].plan_id')
# 5. Confirm the plan
curl -s -X POST "https://pryfox.ai/openapi/projects/$PROJECT/plans/$PLAN_ID/confirm" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-H "X-CSRF-Token: $CSRF" \
-d "{\"confirm\":true,\"workspace_id\":\"$WORKSPACE\"}"
# 6. Subscribe to this workspace's work-group channel real-time event stream (filter by task_id, disconnect on terminal)
# EventSource cannot set custom headers, so token is passed via URL.
# In a JS client use new EventSource(...), filter by data.task_id for your task,
# and es.close() after completed/failed/cancelled (channel-level long-lived connections don't auto-close)
# 7. List all tasks in this workspace
curl -s "https://pryfox.ai/openapi/projects/$PROJECT/tasks?workspace_id=$WORKSPACE&limit=20" \
-H "Authorization: Bearer $TOKEN"
# 8. Fetch historical logs for this workspace's work-group channel (backfill existing events before connecting)
curl -s "https://pryfox.ai/openapi/projects/$PROJECT/channels/logs?channel=work-group&workspace_id=$WORKSPACE&limit=50" \
-H "Authorization: Bearer $TOKEN"