Agents and workflows
Create and manage agents, and schedule, run and monitor workflow tasks through the REST API.
An agent is a saved assistant: a system prompt, a model, the built-in tools it may use, remote MCP servers, knowledge bases and files. A workflow task is an unattended AI run: fixed instructions sent to one model on a cron schedule or on demand, with every run recorded as an execution. These are the API side of Agents and Workflows in the app.
All paths are relative to https://api.stickyprompts.com/api/v1. Every request runs as the user who owns the API key.
Conventions
:idaccepts either the numericIDor theslug. A purely numeric value is always treated as an ID.- Objects serialise their base fields capitalised (
ID,CreatedAt,UpdatedAt,DeletedAt, alwaysnullon live rows); every other field is snake_case. - Page-based lists take
page(default1) andsize(default10), return a bare JSON array, and put totals in theX-Total-Count,X-PageandX-Page-Sizeresponse headers. - Errors are
{"error": "<message>"}. Guardrail and data-residency rejections add a machine code:{"error": "...", "code": "..."}.
Agents
The API manages an agent’s configuration. To chat with an agent, pass its agent_id when you start a conversation, or @mention its tag: see Conversations and messages.
The Agent object
{
"ID": 412,
"CreatedAt": "2026-09-28T09:14:03.512Z",
"UpdatedAt": "2026-09-28T09:14:03.512Z",
"DeletedAt": null,
"user_id": 88,
"name": "Support Triage",
"slug": "k7Xq2bd",
"tag": "support-triage",
"icon": "headset",
"color": "#3B82F6",
"description": "Classifies Northwind support tickets and drafts replies",
"system_prompt": "You are the first-line support agent for Northwind Analytics...",
"ai_model_id": 31,
"ai_model": { "ID": 31, "name": "...", "...": "..." },
"tools": [{ "name": "web_search", "use": true }],
"knowledge_base_limit_node_ids": null,
"world_knowledge": false,
"knowledge_bases": [{ "ID": 7, "...": "..." }]
}
| Field | Type | Notes |
|---|---|---|
user_id | uint | The owner. |
name | string | Up to 500 characters. |
slug | string | Server-generated, usable as :id. |
tag | string | The @handle: ^[a-z0-9][a-z0-9-_]*$, 1-50 characters. |
icon | string | For example cpu, rocket, folder, database, headset. Default cpu. |
color | string | Hex color from the palette, for example #F59E0B (the default). |
description | string or null | Shown on the agent card. |
system_prompt | string | May have been redacted by your workspace’s guardrails. |
ai_model_id, ai_model | uint, object | The model. On GET /agents the ai_model object is not loaded and comes back zero-valued: read ai_model_id there. |
tools | array or null | {"name", "use"}. Names: web_search, image_generation, video_generation, speech_generation, music_generation, remote_mcp. |
knowledge_base_limit_node_ids | uint[] or null | Restricts retrieval to these knowledge-base nodes. |
world_knowledge | bool | Whether the agent may go beyond its knowledge bases. |
mcp_servers, knowledge_bases, files | array | Omitted when empty; not loaded on the list endpoint. |
is_shared | bool | Only on GET /agents and GET /agents/:id: true when someone else owns the agent and shared it with you. |
POST /agents
/api/v1/agents Creates an agent.
-
namestring required - Must not be blank.
-
ai_model_iduint required - Required in practice. Checked against your workspace’s data-residency policy for agents. The model’s existence is not explicitly checked here.
-
tagstring optional - The
@handle. When omitted it is derived fromname(lower-cased, other characters replaced by-, max 50, fallbackagent). -
iconstring optional defaultcpu - Icon name.
-
colorstring optional default#F59E0B - Hex color.
-
descriptionstring optional - One-line description.
-
system_promptstring optional - The job description. Passed through your workspace’s sensitive-data guard.
-
toolsarray optional [{"name", "use"}].-
mcp_server_idsuint[] optional - Remote MCP servers to attach. Only servers you own are attached; others are silently ignored.
-
knowledge_base_idsuint[] optional - Knowledge bases to attach. Only ones you can access are attached; others are silently dropped.
-
file_idsuint[] optional - Files to attach. Only files you own are attached; others are silently ignored.
-
knowledge_base_limit_node_idsuint[] optional - Restrict retrieval to these nodes.
-
world_knowledgebool optional defaultfalse - Allow knowledge beyond the knowledge bases.
curl -X POST https://api.stickyprompts.com/api/v1/agents \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Support Triage",
"tag": "support-triage",
"icon": "headset",
"description": "Classifies Northwind support tickets and drafts replies",
"system_prompt": "You are the first-line support agent for Northwind Analytics. Classify each ticket, rate urgency 1-3 and draft a reply.",
"ai_model_id": 31,
"tools": [{ "name": "web_search", "use": false }],
"knowledge_base_ids": [7]
}'import os, requests
r = requests.post(
"https://api.stickyprompts.com/api/v1/agents",
headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
json={
"name": "Support Triage",
"tag": "support-triage",
"icon": "headset",
"description": "Classifies Northwind support tickets and drafts replies",
"system_prompt": "You are the first-line support agent for Northwind Analytics. "
"Classify each ticket, rate urgency 1-3 and draft a reply.",
"ai_model_id": 31,
"tools": [{"name": "web_search", "use": False}],
"knowledge_base_ids": [7],
},
)
r.raise_for_status()
agent = r.json()
print(agent["ID"], agent["tag"])const agent = await fetch("https://api.stickyprompts.com/api/v1/agents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Support Triage",
tag: "support-triage",
icon: "headset",
description: "Classifies Northwind support tickets and drafts replies",
system_prompt:
"You are the first-line support agent for Northwind Analytics. Classify each ticket, rate urgency 1-3 and draft a reply.",
ai_model_id: 31,
tools: [{ name: "web_search", use: false }],
knowledge_base_ids: [7],
}),
}).then((r) => r.json());
console.log(agent.ID, agent.tag);Response. 201 Created with the Agent object (without is_shared).
Errors. 400 for a non-JSON body, "Agent name is required", an invalid tag, a data-residency violation ({"error": "...", "code": "NO_ENDPOINT_AVAILABLE"}, "NO_PROVIDER_ASSIGNED" and similar) or a sensitive-data violation in system_prompt ({"error", "code"}). 500 "Failed to create agent".
GET /agents
/api/v1/agents Lists agents you own and agents shared with you (directly, through one of your teams, or with your whole workspace). The MCP tool list_agents returns only your own agents.
-
searchstring query optional - Case-insensitive substring match on
name. -
sharedstring query optional false: only agents you own.true: only agents owned by others and shared with you. Omitted: both.-
usage_sortstring query optional trueorders by number of messages sent with the agent, descending. Otherwise no explicit order.-
pageinteger query optional default1 - Page number.
-
sizeinteger query optional default10 - Items per page.
Response. 200 with an array of Agent objects, each with is_shared. On this route ai_model, mcp_servers, knowledge_bases and files are not loaded.
GET /agents/:id
/api/v1/agents/:id Returns one agent you own or that has been shared with you, fully loaded: ai_model, mcp_servers, knowledge_bases, files and is_shared.
-
idstring path required - Agent ID or slug.
Errors. 404 "Agent not found", 403 "You don't have access to this agent".
PATCH /agents/:id
/api/v1/agents/:id Updates an agent. Only the owner can update it; sharing does not grant edit rights. The body takes the same fields as create, all optional: omitted or null fields stay unchanged. name may not be blank; system_prompt goes through the sensitive-data guard and ai_model_id through the data-residency check.
-
idstring path required - Agent ID or slug.
-
namestring optional - New name. Not blank.
-
system_promptstring optional - New system prompt.
-
ai_model_iduint optional - New model.
-
mcp_server_idsuint[] optional - Omitted: leave as is. Any array, including
[], replaces the whole set. -
knowledge_base_idsuint[] optional - Omitted: leave as is. Any array, including
[], replaces the whole set. -
file_idsuint[] optional - Omitted: leave as is. Any array, including
[], replaces the whole set. -
tagstring optional - As on create.
-
iconstring optional - As on create.
-
colorstring optional - As on create.
-
descriptionstring optional - As on create.
-
toolsarray optional - As on create.
-
knowledge_base_limit_node_idsuint[] optional - As on create.
-
world_knowledgebool optional - As on create.
Response. 200 with the fully loaded Agent object (no is_shared).
Errors. 400, 403 "You are not authorized to update this agent", 404 "Agent not found", 500.
DELETE /agents/:id
/api/v1/agents/:id Soft-deletes an agent you own. Its links to MCP servers, knowledge bases and files are cleared; the files and knowledge bases themselves are not deleted.
-
idstring path required - Agent ID or slug.
Response. 204 No Content. Errors. 403 "You are not authorized to delete this agent".
Workflow tasks
A workflow task sends its instructions to one model, unattended. How a run works:
- An execution is created immediately with
status: "running"; the work happens in the background. - The run first checks the owner’s quota (failing with
error_text: "quota exceeded: <reason>"), then creates a hidden, non-interactive conversation with the task’s model and sendsinstructionsas the message. - Every built-in tool (
web_search,image_generation,video_generation,speech_generation,music_generation) and every remote MCP server the owner has configured is enabled. The model ignores tools it does not support. - A run has a hard timeout of 45 minutes (
error_text: "The run took too long to complete"). - On success, the final answer is stored in
result_text. Executions interrupted by a server restart are markederrorwith"The run was interrupted before it could finish"and are not resumed.
Triggers. The only trigger type is cron. There are no webhook, event or API-call triggers: a run starts either from a cron trigger or from POST /workflow-tasks/:id/manual-run. There are no outbound webhooks either, so completion must be polled.
Execution statuses. running (in progress), success (finished, result_text set) and error (finished, error_text set). success and error are terminal. pending exists as a value but the current run code does not set it.
The Workflow task object
{
"ID": 57,
"CreatedAt": "2026-09-28T09:20:11Z",
"UpdatedAt": "2026-09-28T09:20:11Z",
"DeletedAt": null,
"user_id": 88,
"name": "Monday pipeline digest",
"slug": "Tq9mZr4b",
"description": null,
"ai_model_id": 31,
"ai_model": { "ID": 31, "...": "..." },
"instructions": "Summarise last week's new deals in our CRM and email the summary to the Northwind sales team.",
"enabled": true,
"triggers": [
{
"ID": 90,
"workflow_task_id": 57,
"type": "cron",
"enabled": true,
"cron_expression": "0 8 * * 1",
"timezone": "Europe/Budapest",
"last_triggered_at": null,
"next_trigger_at": "2026-09-28T08:00:00+02:00"
}
]
}
| Field | Notes |
|---|---|
instructions | The prompt sent on every run, after sensitive-data redaction. |
enabled | A disabled task’s cron triggers do not fire. |
triggers | Omitted when empty. Each has type (cron), enabled, cron_expression, timezone (IANA, default UTC), last_triggered_at and next_trigger_at (null while the trigger is disabled). |
total_run_count | Only on GET /workflow-tasks/:id: executions ever recorded. |
success_rate | Only on GET /workflow-tasks/:id: share of success among the 30 most recent executions, 0-1 (0 when there are none). |
POST /workflow-tasks
/api/v1/workflow-tasks Creates a workflow task.
-
namestring required - Not blank.
-
ai_model_iduint required - Must exist (
400 "AI model not found") and pass the data-residency policy for workflows. -
instructionsstring required - Not blank. Passed through the sensitive-data guard.
-
descriptionstring optional - Free text.
-
enabledbool optional defaulttrue - Whether the task’s triggers fire.
-
triggersarray optional - Trigger requests, see below.
-
typestring required - Must be
cron. Anything else gives400 "unsupported trigger type: <x>". -
cron_expressionstring required - Standard 5-field cron: minute, hour, day of month, month, day of week. Invalid gives
400 "invalid cron_expression: ...". -
enabledbool optional defaultfalse - Defaults to false. A trigger sent without
"enabled": trueis saved disabled and never fires. -
timezonestring optional defaultUTC - IANA zone such as
Europe/Budapest. Invalid gives400 "invalid timezone: ...".
curl -X POST https://api.stickyprompts.com/api/v1/workflow-tasks \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Monday pipeline digest",
"ai_model_id": 31,
"instructions": "Summarise last week'"'"'s new deals in our CRM and email the summary to the Northwind sales team.",
"triggers": [
{ "type": "cron", "enabled": true, "cron_expression": "0 8 * * 1", "timezone": "Europe/Budapest" }
]
}'import os, requests
r = requests.post(
"https://api.stickyprompts.com/api/v1/workflow-tasks",
headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
json={
"name": "Monday pipeline digest",
"ai_model_id": 31,
"instructions": "Summarise last week's new deals in our CRM and email "
"the summary to the Northwind sales team.",
"triggers": [{"type": "cron", "enabled": True,
"cron_expression": "0 8 * * 1", "timezone": "Europe/Budapest"}],
},
)
r.raise_for_status()
print(r.json()["triggers"][0]["next_trigger_at"])const task = await fetch("https://api.stickyprompts.com/api/v1/workflow-tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Monday pipeline digest",
ai_model_id: 31,
instructions:
"Summarise last week's new deals in our CRM and email the summary to the Northwind sales team.",
triggers: [{ type: "cron", enabled: true, cron_expression: "0 8 * * 1", timezone: "Europe/Budapest" }],
}),
}).then((r) => r.json());
console.log(task.ID, task.triggers[0].next_trigger_at);Response. 201 with the Workflow task (without run stats).
Errors. 400 (validation, trigger errors, data-residency {"error", "code"}, sensitive-data {"error", "code"}), 500.
GET /workflow-tasks
/api/v1/workflow-tasks Lists your own workflow tasks (they cannot be shared), newest first. Each item includes ai_model and triggers, but not total_run_count or success_rate.
-
searchstring query optional - Case-insensitive substring match on
name. -
pageinteger query optional default1 - Page number.
-
sizeinteger query optional default10 - Items per page.
Response. 200 with an array of Workflow task objects.
GET /workflow-tasks/:id
/api/v1/workflow-tasks/:id Returns one of your workflow tasks with ai_model, triggers, total_run_count and success_rate.
-
idstring path required - Workflow task ID or slug.
Errors. 404 "Workflow task not found", 403 "You don't have access to this workflow task".
PATCH /workflow-tasks/:id
/api/v1/workflow-tasks/:id Updates one of your workflow tasks. Every field is optional; omitted means unchanged.
-
idstring path required - Workflow task ID or slug.
-
namestring optional - Not blank.
-
descriptionstring optional - Free text.
-
ai_model_iduint optional - New model.
-
instructionsstring optional - Not blank.
-
enabledbool optional - Switching from
falsetotruerecomputes every enabled trigger’snext_trigger_atfrom now. -
triggersarray optional - When present, replaces the entire trigger set: old triggers are deleted and unscheduled.
[]removes all triggers.
curl -X PATCH https://api.stickyprompts.com/api/v1/workflow-tasks/57 \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'
Response. 200 with the Workflow task (with ai_model and triggers, without run stats).
Errors. 400, 403 "You are not authorized to update this workflow task", 404, 500.
DELETE /workflow-tasks/:id
/api/v1/workflow-tasks/:id Unschedules and deletes the triggers, and soft-deletes the task and its executions. Owner only.
-
idstring path required - Workflow task ID or slug.
Response. 204 No Content.
POST /workflow-tasks/:id/manual-run
/api/v1/workflow-tasks/:id/manual-run Starts a run now, whether or not the task or its triggers are enabled. Owner only. No body. Returns 202 Accepted with the new execution while the run continues in the background. There is no callback: poll GET /workflow-tasks/:id/executions until the execution’s status is success or error.
-
idstring path required - Workflow task ID or slug.
curl -X POST https://api.stickyprompts.com/api/v1/workflow-tasks/57/manual-run \
-H "Authorization: Bearer $STICKY_API_KEY"
# Then poll the run history until the execution is success or error
curl "https://api.stickyprompts.com/api/v1/workflow-tasks/57/executions?page=1&size=5" \
-H "Authorization: Bearer $STICKY_API_KEY"import os, time, requests
BASE = "https://api.stickyprompts.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"}
run = requests.post(f"{BASE}/workflow-tasks/57/manual-run", headers=H).json()
while True:
time.sleep(10)
executions = requests.get(f"{BASE}/workflow-tasks/57/executions",
headers=H, params={"page": 1, "size": 10}).json()
ex = next((e for e in executions if e["ID"] == run["ID"]), None)
if ex and ex["status"] in ("success", "error"):
print(ex["status"], ex["result_text"] or ex["error_text"])
breakconst BASE = "https://api.stickyprompts.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.STICKY_API_KEY}` };
const run = await fetch(`${BASE}/workflow-tasks/57/manual-run`, { method: "POST", headers }).then((r) => r.json());
for (;;) {
await new Promise((r) => setTimeout(r, 10_000));
const executions = await fetch(`${BASE}/workflow-tasks/57/executions?page=1&size=10`, { headers }).then((r) => r.json());
const ex = executions.find((e) => e.ID === run.ID);
if (ex && (ex.status === "success" || ex.status === "error")) {
console.log(ex.status, ex.result_text ?? ex.error_text);
break;
}
}Response. 202 Accepted:
{
"ID": 1203,
"CreatedAt": "2026-09-28T09:31:00Z",
"UpdatedAt": "2026-09-28T09:31:00Z",
"DeletedAt": null,
"workflow_task_id": 57,
"trigger_type": null,
"conversation_context_id": null,
"status": "running",
"started_at": "2026-09-28T09:31:00Z",
"finished_at": null,
"result_text": null,
"error_text": null
}
conversation_context_id is null here because the hidden conversation is attached a moment later.
Errors. 404, 403 "You are not authorized to run this workflow task", 500 "Failed to start workflow run". Quota and model failures do not fail the HTTP call; they show up later as status: "error" on the execution.
GET /workflow-tasks/:id/executions
/api/v1/workflow-tasks/:id/executions Returns a task’s run history, newest first. Owner only.
-
idstring path required - Workflow task ID or slug.
-
pageinteger query optional default1 - Page number.
-
sizeinteger query optional default10 - Items per page.
Response. 200 with an array of executions. trigger_type is null for a manual run and "cron" for a scheduled one. result_text is returned as rendered HTML.
[
{
"ID": 1203,
"workflow_task_id": 57,
"trigger_type": null,
"conversation_context_id": 88123,
"status": "success",
"started_at": "2026-09-28T09:31:00Z",
"finished_at": "2026-09-28T09:32:14Z",
"result_text": "<p>Last week 4 new deals were created...</p>",
"error_text": null
}
]
Related
- Conversations and messages - chat with an agent through
agent_idor an@tag. - Integrations - the CRM, email and database connections a workflow can use.
- MCP tool reference -
list_agents,get_agent,list_workflow_tasks,get_workflow_task,run_workflow_task.