Base URL
http://localhost:3333/api/· Authentication · Conventions
Agent Routes
Base path: /api/agents
Health Check
GET /health
- Authentication: None
- Description: Check if the agent service is running
- Response:
{
"status": "OK"
}Get All Agents
GET /
- Authentication: Required
- Description: Retrieve all agents for the authenticated user- Note: Include trailing slash (
/api/agents/) for best compatibility - Response:
{
"agents": [
{
"id": "67b9bf15-a5c7-4153-936b-5959dc83b03c",
"name": "Content Manager",
"description": "Main content manager",
"status": "active",
"icon": "data:image/png;base64,...",
"category": "Content & Media",
"provider": "openai",
"model": "gpt-4o",
"assignedTools": ["web_search"],
"assignedWorkflows": [],
"systemPrompt": "Persona / directives text",
"assignedSkills": [],
"toolAccessMode": "restricted",
"resourceId": 1,
"creditsUsed": 0,
"creditLimit": 1000,
"workflows": 0,
"lastActive": null,
"successRate": null
}
]
}Important: The response wraps agents in an agents array property, not a direct array. icon can be a large base64 data URL — use GET /summary for lightweight lists.
Get All Agents (Summary)
GET /summary
- Authentication: Required
- Description: Lightweight agent list. Drops the three biggest per-row payload contributors (base64
icon,systemPrompt, and the tools/workflows/skills arrays) and returns counts instead. Use for rendering agent cards; hitGET /:idwhen you need the full record. - Response:
{
"agents": [
{
"id": "67b9bf15-a5c7-4153-936b-5959dc83b03c",
"name": "Content Manager",
"description": "Main content manager",
"status": "active",
"category": "Content & Media",
"provider": "openai",
"model": "gpt-4o",
"toolCount": 3,
"skillCount": 1,
"workflows": 0,
"creditsUsed": 0,
"creditLimit": 1000,
"lastActive": null,
"successRate": null,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z"
}
]
}Save/Update Agent
POST /save
- Authentication: Required- Description: Create a new agent or update an existing one
- Body:
{
"id": "optional-agent-id",
"name": "Agent Name",
"description": "Agent description",
"systemPrompt": "Optional persona / directives",
"assignedTools": ["web_search", "web_scrape"],
"assignedSkills": ["skill-id-or-slug"],
"toolAccessMode": "restricted",
"config": {}
}toolAccessMode (string, "restricted" | "open", default "restricted") controls the agent's runtime tool surface in chat:
| Mode | Tool surface |
|---|---|
restricted (default) |
assignedTools are the ceiling. The agent sees only its assigned tools plus a baseline (web_search, skill activation via activate_skill, memory read/write via recall/save_agent_memory/get_agent_memories, discover_tools) and universal primitives. |
open |
Full main-chat dynamic tool surface (default tools + keyword-triggered groups + persistent discover_tools loads), with assignedTools pinned always-on regardless of keyword matching. |
Any value other than "open" is sanitized to "restricted" on save. In both modes the agent's system prompt is built by the unified prompt builder: persona-first, full skills catalog with the agent's assignedSkills highlighted as specialty skills, and persistent agent-scoped memory.- Response (200):
{
"message": "New agent created",
"agentId": "agent-id"
}message is "New agent created" or "Agent updated". If id is omitted (or belongs to another user's agent), a new agent is created with a fresh UUID. New agents inherit the user's selected provider/model when provider/model are not supplied; if neither the body nor user settings yield a provider+model the request fails with 400.
Get Agent by ID
GET /:id
- Authentication: Required
- Parameters:
id(path): Agent ID- Description: Retrieve a specific agent by ID (full record, owner only —403otherwise,404if not found)
- Response:
{
"id": "agent-id",
"name": "Agent Name",
"description": "Agent description",
"status": "active",
"icon": "data:image/png;base64,...",
"category": "Content & Media",
"provider": "openai",
"model": "gpt-4o",
"systemPrompt": "Persona / directives text",
"assignedTools": ["web_search"],
"assignedWorkflows": [],
"assignedSkills": [],
"toolAccessMode": "restricted",
"created_by": "user-id",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}Note: this endpoint returns the raw DB row plus parsed arrays — timestamps are snake_case (created_at), unlike the camelCase list endpoints.
Update Agent
PUT /:id
- Authentication: Required
- Parameters:
id(path): Agent ID
- Description: Same handler as
POST /save— identical body fields (name, description, systemPrompt, assignedTools, assignedSkills, assignedWorkflows, toolAccessMode, provider, model, icon, category, status, creditLimit) and identical response:
{
"message": "Agent updated",
"agentId": "agent-id"
}Delete Agent
DELETE /:id
- Authentication: Required
- Parameters:
id(path): Agent ID- Description: Delete an agent by ID. Also deletes the agent's memories and broadcastsAGENT_DELETEDto the user's connected clients.
- Response:
{
"message": "Agent <id> deleted successfully."
}Chat with Agent
POST /:id/chat
- Authentication: Required
- Parameters:
id(path): Agent ID- Note: Agent chats run through the same unified orchestrator as the main chat — persona-first system prompt, full skills catalog (assigned skills highlighted), persistent agent-scoped memory, and the tool surface determined by the agent'stoolAccessMode(see Save/Update Agent). External bridges (Discord, Mattermost, etc.) get identical behavior through this endpoint.
- Body:
{
"message": "Your message here",
"history": [{ "role": "user", "content": "earlier turn" }],
"conversationId": "optional-stable-conversation-key",
"provider": "optional-override",
"model": "optional-override",
"enabledTools": ["web_search"]
}All fields except message are optional. provider/model default to the agent's saved provider/model, then the user's selected settings. conversationId keys persistent conversation history. enabledTools narrows (never widens) the agent's tool surface.
- Response: Server-sent events stream (same event vocabulary as Universal Chat —
conversation_started,content_delta,tool_start/tool_end,final_content,done, …). There is no JSON response mode.
Stream Chat with Agent
POST /:id/chat-stream
- Authentication: Required
- Parameters:
id(path): Agent ID
- Description: Identical to
POST /:id/chat(both routes call the same universal handler and both stream). Kept for backward compatibility. - Response: Server-sent events stream
Get Agent Suggestions
POST /:id/suggestions
- Authentication: Required
- Parameters:
id(path): Agent ID
- Body:
{
"lastUserMessage": "the user's last message",
"lastAssistantMessage": "the agent's last reply",
"history": []
}provider/model are auto-filled from the agent's saved config; the agent's persona and assigned-tool list are injected into the suggestion prompt.
- Response (JSON, not a stream):
{
"suggestions": [
{ "id": "suggestion_1", "text": "Draft the outline", "icon": "\ud83d\udcdd" },
{ "id": "suggestion_2", "text": "Search for sources", "icon": "\ud83d\udd0d" },
{ "id": "suggestion_3", "text": "Summarize so far", "icon": "\ud83d\udccb" }
]
}On LLM failure the endpoint still returns 200 with three generic fallback suggestions plus an error field.
Export Agent
GET /:id/export
- Authentication: Required (owner only —
403otherwise) - Parameters:
id(path): Agent ID
- Description: Export a portable agent envelope (persona, provider/model, assigned tools/skills/workflows by name/id — no memories, no credentials).
- Response:
{
"_format": "agnt-agent",
"_version": 1,
"payload": {
"name": "Agent Name",
"description": "...",
"icon": null,
"provider": "openai",
"model": "gpt-4o",
"systemPrompt": "...",
"category": "",
"status": "ACTIVE",
"creditLimit": 1000,
"assignedTools": ["web_search"],
"assignedSkills": [],
"assignedWorkflows": []
},
"exported_at": "2026-01-01T00:00:00Z"
}Import Agent
POST /import
- Authentication: Required
- Body: An export envelope (either the envelope itself or wrapped as
{ "envelope": { ... } }) - Response (
201):
{
"success": true,
"agentId": "new-agent-id",
"missingRefs": { "tools": [], "skills": [], "workflows": [] }
}missingRefs lists referenced tools/skills/workflows that don't exist in this installation (the agent is still created). Malformed envelopes return 400.
Orchestrator Routes
Base path: /api/orchestrator
Health Check
GET /health
- Authentication: None
- Description: Check if the orchestrator service is running
- Response:
{
"status": "OK"
}Get Available Tools
GET /tools
- Authentication: Required
- Description: Get the list of tools available to the orchestrator (native tools, registry tools, and installed plugin tools). Used by the frontend tool selector to render available actions.
- Response:
{
"tools": [
{
"name": "tool_name",
"description": "What the tool does",
"parameters": { "type": "object", "properties": {} }, "category": "native|plugin|registry"
}
]
}Universal Chat
POST /chat
- Authentication: Required
- Content-Type:
multipart/form-data(or JSON when no files are attached) - Body (common parameters, all optional except
message):message(string): Chat messagemessages(array): Full message array (alternative tomessage+history)history(array): Prior turns as{ role, content }objectsconversationId(string): Stable key for persistent conversation state (context, loaded tool groups, activated skills)provider/model(string): LLM override. Resolution order: body → agent record (saved-agent chats) → user's selected settings → first provider with valid credentialsenabledTools(array): Explicit tool whitelist from the tool selector. Narrows the surface; an empty array means zero toolsreasoningEnabled(boolean) /reasoningValue(string): Extended-thinking controls for reasoning-capable modelsfiles(file[]): Optional file attachments (max 20MB each)- Surface-context parameters (any one of these routes the chat to that surface):
agentId/agentContext/agentState,workflowId/workflowContext/workflowState,toolId/toolContext/toolState,widgetId/widgetContext/widgetState,goalId/goalContext,codeId/codeContext,skillId/skillInstructions/skillName/skillDescription/skillAllowedTools
- Description: Universal chat handler. The chat type is detected from the route path and/or which context parameters are present; all typed routes below share this handler and differ only in the injected page context.
- Response: Server-sent events stream. Key named events:
| Event | Payload highlights |
|---|---|
conversation_started |
conversationId |
files_processed |
attached-file summaries |
assistant_message |
assistantMessageId for the turn |
content_delta / reasoning_delta |
delta, accumulated text |
tool_pending / tool_start / tool_end |
tool call id, name, args / result / error |
tool_executions |
per-round tool execution details |
image_generated |
image refs for generated images |
data_content / data_offloaded |
large tool results moved to the offload store |
context_status / context_managed |
context-window usage / trimming info |
steering_applied |
mid-run user steering acknowledged |
frontend_event |
UI-targeted tool side effects (highlights, tours) |
error |
error message for the turn |
final_content |
complete assistant response text |
done |
stream end |
Agent Chat
POST /agent-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body: Universal Chat parameters plus:
agentId(string): A saved agent ID for a persona chat, or the sentinel"agent-chat"for the AgentForge builder surface (Annie persona + agent-management functions)
- Description: Saved-agent chats get the unified prompt (agent persona first, full skills catalog with specialty highlights, agent-scoped memory) and the tool surface defined by the agent's
toolAccessMode. Equivalent toPOST /api/agents/:id/chat, which resolves the agent server-side. - Response: Server-sent events stream
Workflow Chat
POST /workflow-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body:
workflowId(string): Workflow IDmessage(string): Chat messagefiles(file[]): Optional file attachments
- Description: Chat with a specific workflow
- Response: Server-sent events stream
Tool Chat
POST /tool-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body:
toolId(string): Tool IDmessage(string): Chat messagefiles(file[]): Optional file attachments
- Description: Chat with a specific tool
- Response: Server-sent events stream
Goal Chat
POST /goal-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body:
goalId(string): Goal IDmessage(string): Chat messagefiles(file[]): Optional file attachments
- Description: Chat with a specific goal
- Response: Server-sent events stream
Artifact Chat
POST /artifact-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body:
message(string): Chat messagecodeContext(object, optional): Context from the artifacts workspace (active file, selection, open files)files(file[]): Optional file attachments (max 20MB each)
- Description: Chat handler for the Artifacts workspace ("Annie" assistant). Streams responses and calls the workspace file-operation tools (
read_file,write_file,edit_file,list_files) against the user's workspace root. See the Artifacts section for full details on storage, tools, and events. - Response: Server-sent events stream
Widget Chat
POST /widget-chat
- Authentication: Required
- Content-Type:
multipart/form-data - Body:
message(string): Chat messagefiles(file[]): Optional file attachments (max 20MB each)
- Description: Widget-specific chat with streaming. Used for creating and editing custom dashboard widgets.
- Response: Server-sent events stream### Get Suggestions
POST /suggestions
- Authentication: Required
- Body:
{
"lastUserMessage": "the user's last message",
"lastAssistantMessage": "the assistant's last reply",
"history": [],
"provider": "openai",
"model": "gpt-4o",
"agentContext": {}
}provider and model are required (400 without them). agentContext is optional and shapes suggestions to an agent's persona/tools.
- Response (JSON, not a stream):
{
"suggestions": [
{ "id": "suggestion_1", "text": "Draft the outline", "icon": "\ud83d\udcdd" },
{ "id": "suggestion_2", "text": "Search for sources", "icon": "\ud83d\udd0d" },
{ "id": "suggestion_3", "text": "Summarize so far", "icon": "\ud83d\udccb" }
]
}On LLM failure the endpoint returns 200 with three generic fallback suggestions plus an error field.
Async Tool Routes
Base path: /api/async-tools
\u26A0\uFE0F Critical Behaviour Notes (read before integrating)
These behaviours were discovered during empirical stress-testing on 2026-05-04. Most of the silent-degradation cases were fixed in the same dated commit \u2014 see `ASYNC-TOOLS-REFERENCE.md` \u00A7 Recent Fixes for the fix table.
\ud83d\udd34 In-batch ordering is NOT guaranteed. When multiple async tool calls are submitted in the same function-calls block, the orchestrator fires them all in the same millisecond with no ordering. Submitting a
writefollowed by areadof the same path can produce a read failure (ENOENT) 2+ seconds before the write completes. This is by design \u2014 async tools are independent and unordered. Use synchronous calls for any read-after-write or dependent operation. Non-LLM integrators (workflow nodes, webhook handlers, third-party callers) must enforce ordering themselves: await the queued result before issuing the next dependent call.\u2705 Silent-degradation parameter patterns FIXED (2026-05-04). The four shapes below used to silently fall back to a one-shot. They now return a structured validation error at queue time so callers can self-correct:
_interval: 0(or negative / non-numeric) \u2014 rejected._stopAfter: Nwithout_interval\u2014 rejected._duration: Nwithout_interval\u2014 rejected._delayFirst: truewithout_interval\u2014 rejected.\u2705 Failure observability FIXED (2026-05-04). The
GET /api/async-tools/statusendpoint now reports three failure-related counters:
failed\u2014 system-level failures only (worker crashed/aborted). Same semantics as before.businessFailed\u2014 completed executions whose innerresult.success === false(ENOENT, EPERM, per-iteration errors in periodic runs).totalFailed\u2014failed + businessFailed, exact (the source sets are disjoint).\u2705 Autonomous-message banner FIXED (2026-05-04). The wrapper that delivers async results into the conversation now inspects the inner result and emits
\u26A0\uFE0F ASYNC TOOL FINISHED WITH ERRORwith an honesty directive when the operation reported failure, instead of always claiming success.\u2705 Sub-second intervals work (validated down to
_interval: 0.1), but at intervals shorter than the tool\u2019s own execution time the actual gap is dominated by tool throughput, not the timer. Note:_interval: 0is no longer accepted (see fix above).\u2705 True OS-level concurrency is real \u2014 five parallel
pingshells took 5.2 s total instead of 21 s.\u26a0\ufe0f Whole feature is experimental and OFF by default (2026-05-04). A per-user setting (
asyncToolsEnabledonPUT /api/users/settings) gates whether the LLM can use async tools at all. With it OFF (default), the universal async control params drop off every tool schema AND the async-guidance prompt section is omitted, so the LLM has no way to know async exists. In-flight tasks that started before the toggle flipped run to completion. New users see the chat behave like a conventional sync-only assistant until they explicitly enable the feature in Settings. See `ASYNC-TOOLS-REFERENCE.md` \u00a7 Async tools toggle for the full contract.
Tool-Call Async Parameters (How to Use Async)
Every tool in the AGNT registry — native, plugin, registry, MCP — supports background and recurring execution via a set of universal underscore-prefixed parameters. These are not part of the tool's own schema; they are intercepted by the orchestrator before the tool runs.
📘 For the comprehensive guide with worked examples, error handling patterns, edge cases, and empirically-validated test results, see `ASYNC-TOOLS-REFERENCE.md`.
Universal Async Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
_executeAsync |
boolean | false |
Run the tool in the background. Returns an executionId immediately; results arrive later via autonomous message. |
_interval |
integer (seconds) | — | Re-run the tool every N seconds. Requires _executeAsync. |
_stopAfter |
integer | — | Stop after N iterations. Requires _interval. |
_duration |
number (minutes) | — | Stop after N minutes total. Decimals allowed (e.g. 0.1 = 6 seconds). Requires _interval. |
_delayFirst |
boolean | false |
Skip the immediate first run — wait one full _interval before the first execution. Requires _interval. |
_estimatedMinutes |
number | — | UI hint for expected duration. No functional impact on scheduling — purely cosmetic. |
Common Patterns
Run once in the background:
{ "query": "latest AI news", "_executeAsync": true }Run a real tool once after a delay (e.g. send a reminder email in 1 hour):
{
"to": "user@example.com",
"subject": "Reminder",
"body": "Don't forget!",
"_executeAsync": true,
"_interval": 3600,
"_stopAfter": 1,
"_delayFirst": true
}Run every 60 seconds, exactly 5 times:
{ "...": "...", "_executeAsync": true, "_interval": 60, "_stopAfter": 5 }Scrape a site every 5 minutes for 1 hour:
{ "url": "https://example.com", "_executeAsync": true, "_interval": 300, "_duration": 60 }Safety-belt pattern (stop at whichever limit hits first):
{ "...": "...", "_executeAsync": true, "_interval": 60, "_stopAfter": 100, "_duration": 60 }Response Shapes
Sync (no async params) — returns the tool's native payload directly:
{ "success": true, "total": 20, "individualRolls": [20], "error": null }Async, queued — returned immediately when _executeAsync: true:
{
"success": true,
"status": "queued",
"executionId": "8604c5b0-8515-4cb1-8894-918acb31ac25",
"message": "<toolName> started in the background. You'll receive updates as it progresses.",
"estimatedDuration": null
}Async, completed (one-shot) — arrives via autonomous message:
{
"success": true,
"status": "completed",
"executionId": "8604c5b0-8515-4cb1-8894-918acb31ac25",
"result": "{\"success\":true,\"total\":20,...}",
"duration": 480
}The
resultfield is often a JSON-stringified payload. Clients mustJSON.parse()it before consuming.
Periodic execution completed — a single combined payload after the full schedule finishes:
{
"success": true,
"status": "completed",
"executionId": "af4cd05e-0c19-4e3b-96fb-fc38c57d81bc",
"result": {
"periodicExecution": true,
"totalIterations": 3,
"results": [
{ "iteration": 1, "result": "{...}", "timestamp": 1777909033895 },
{ "iteration": 2, "result": "{...}", "timestamp": 1777909043897 },
{ "iteration": 3, "result": "{...}", "timestamp": 1777909053906 }
],
"totalDuration": 20011
},
"duration": 20011
}Two-Layer Status Model (Critical)
Async responses have two independent layers that must both be checked:
- System layer — the outer envelope (
success,status). Tells you whether the orchestrator queued/ran the task. - Business-logic layer —
result.successor the parsed payload. Tells you whether the tool's operation actually succeeded.
A task can be status: "completed" at the system level while result.success: false at the business layer (e.g. file not found, invalid parameter, API error).
if (response.status === "completed") {
const inner = typeof response.result === "string"
? JSON.parse(response.result)
: response.result;
if (inner.success) {
// use inner data
} else {
// handle inner.error (e.g. ENOENT, EPERM, validation message)
}
}Empirically-Verified Behaviors
These behaviors were validated via live tool calls (see ASYNC-TOOLS-REFERENCE.md for the full test log):
- Validation runs at execution time, not queue time. Invalid parameters are queued successfully and only fail when the task actually runs.
- Conflicting stop conditions: when both
_stopAfterand_durationare set, whichever limit triggers first wins. Tested with_stopAfter: 100+_duration: 0.1(6 s) — task stopped at 3 iterations. - Parallel concurrency: 19+ simultaneous async calls in a single function-calls block all queue and execute cleanly. No rate limit observed.
- Mixed sync + async in the same function-calls block works — sync results return inline, async return execution IDs.
- Periodic results are batched. A periodic task delivers no output until the full schedule completes. Use separate async calls (one per assistant message) if streaming is required.
_estimatedMinutesis purely cosmetic. Tasks run as fast as they can regardless of the estimate.- Minimum interval tested: 1 second (with ~10–20 ms drift per iteration). Sub-second intervals untested.
- Decimal
_durationvalues work (e.g.0.1= 6 seconds). - Error envelope: business-logic failures (
ENOENT,EPERM, validation errors) surface insideresult.errorwhile the outerstatusremains"completed". The system never crashes or hangs on bad input.
Anti-Patterns
❌ Do not use async for dependent tasks. If Task B references Task A's output, Task A must be sync.
❌ Do not build a fake sleep / echo / timer tool to schedule something later. Attach _executeAsync, _interval, _stopAfter: 1, _delayFirst: true directly to the real tool you want to run.
❌ Do not start a periodic task without _stopAfter or _duration. It will run until cancelled via POST /cancel/:executionId.
Get Queue Status
GET /status
- Authentication: Required
- Description: Get async tool queue statistics
- Response:
{
"success": true,
"stats": {
"pending": 0,
"running": 2,
"completed": 15,
"failed": 1
}
}Get Executions by Conversation
GET /executions/:conversationId
- Authentication: Required
- Parameters:
conversationId(path): Conversation ID
- Description: Get all async tool executions for a conversation
- Response:
{
"success": true,
"executions": [
{
"executionId": "exec-id",
"toolName": "tool-name",
"status": "running|completed|failed|cancelled",
"startedAt": "2024-01-01T00:00:00Z",
"completedAt": null
}
]
}Get Running Executions
GET /executions/:conversationId/running
- Authentication: Required
- Parameters:
conversationId(path): Conversation ID
- Description: Get only running async tool executions for a conversation
- Response:
{
"success": true,
"executions": []
}Get Execution Details
GET /execution/:executionId
- Authentication: Required
- Parameters:
executionId(path): Execution ID
- Description: Get details of a specific async tool execution
- Response:
{
"success": true,
"execution": {
"executionId": "exec-id",
"toolName": "tool-name",
"status": "completed",
"result": {}
}
}- Error (404): Execution not found
Cancel Execution
POST /cancel/:executionId
- Authentication: Required
- Parameters:
executionId(path): Execution ID
- Description: Cancel a running async tool execution
- Response:
{
"success": true,
"message": "Async tool execution cancelled successfully"
}Cancel All Executions for Conversation
POST /cancel-all/:conversationId
- Authentication: Required
- Parameters:
conversationId(path): Conversation ID
- Description: Cancel all running async tools for a conversation (global stop)
- Response:
{
"success": true,
"cancelled": 3
}Group Routes
Base path: /api/groups
Groups organize content outputs (artifacts, generated assets) into a hierarchical tree. They support nesting via parent_id, custom sort order, and color-coding. All mutations broadcast realtime events (GROUP_CREATED, GROUP_UPDATED, GROUP_DELETED, CONTENT_UPDATED) to connected clients of the same user.
List Groups
GET /
- Authentication: Required
- Description: Retrieve all groups belonging to the authenticated user
- Response:
{
"groups": [
{
"id": "group-id",
"user_id": "user-id",
"name": "Group Name",
"description": "Optional description",
"color": "#6366f1",
"sort_order": 0,
"parent_id": null,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
]
}Create Group
POST /
- Authentication: Required
- Body:
{
"name": "Group Name",
"description": "Optional description",
"color": "#6366f1",
"sort_order": 0,
"parent_id": "optional-parent-group-id"
}- Description: Create a new group.
nameis required; all other fields are optional.colordefaults to#6366f1,sort_orderto0,parent_idtonull(top-level). - Response:
201 Createdwith the full group object
Update Group
PUT /:id
- Authentication: Required
- Parameters:
id(path): Group ID
- Body: Any subset of
name,description,color,sort_order,parent_id - Description: Update fields on a group
- Response: Updated group object, or
404if not found
Delete Group
DELETE /:id
- Authentication: Required
- Parameters:
id(path): Group ID
- Query Parameters:
mode(string, optional):move(default) reparents direct children to this group's parent (or root);deletelets theON DELETE CASCADEremove child groups along with this one.
- Description: Delete a group. Use
mode=moveto preserve children,mode=deleteto remove the entire subtree. - Response:
{
"message": "Group deleted"
}Reorder Groups
PATCH /reorder
- Authentication: Required
- Body:
{
"orders": [
{ "id": "group-id-1", "sort_order": 0 },
{ "id": "group-id-2", "sort_order": 1 }
]
}- Description: Update
sort_orderfor multiple groups in a single call - Response:
{ "success": true }Move Content Output to Group
PATCH /move/:outputId
- Authentication: Required
- Parameters:
outputId(path): Content output ID
- Body:
{
"group_id": "target-group-id-or-null"
}- Description: Move a single content output into a group. Pass
group_id: nullto ungroup (move to root). - Response:
{ "success": true }Bulk Move Content Outputs
PATCH /bulk-move
- Authentication: Required
- Body:
{
"output_ids": ["output-id-1", "output-id-2"],
"group_id": "target-group-id-or-null"
}- Description: Move multiple content outputs into a group in a single call
- Response:
{ "success": true }