Agents and workflows

Create and manage agents, and schedule, run and monitor workflow tasks through the REST API.

8 min read · Updated 28 September 2026

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

  • :id accepts either the numeric ID or the slug. A purely numeric value is always treated as an ID.
  • Objects serialise their base fields capitalised (ID, CreatedAt, UpdatedAt, DeletedAt, always null on live rows); every other field is snake_case.
  • Page-based lists take page (default 1) and size (default 10), return a bare JSON array, and put totals in the X-Total-Count, X-Page and X-Page-Size response 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, "...": "..." }]
}
FieldTypeNotes
user_iduintThe owner.
namestringUp to 500 characters.
slugstringServer-generated, usable as :id.
tagstringThe @handle: ^[a-z0-9][a-z0-9-_]*$, 1-50 characters.
iconstringFor example cpu, rocket, folder, database, headset. Default cpu.
colorstringHex color from the palette, for example #F59E0B (the default).
descriptionstring or nullShown on the agent card.
system_promptstringMay have been redacted by your workspace’s guardrails.
ai_model_id, ai_modeluint, objectThe model. On GET /agents the ai_model object is not loaded and comes back zero-valued: read ai_model_id there.
toolsarray or null{"name", "use"}. Names: web_search, image_generation, video_generation, speech_generation, music_generation, remote_mcp.
knowledge_base_limit_node_idsuint[] or nullRestricts retrieval to these knowledge-base nodes.
world_knowledgeboolWhether the agent may go beyond its knowledge bases.
mcp_servers, knowledge_bases, filesarrayOmitted when empty; not loaded on the list endpoint.
is_sharedboolOnly on GET /agents and GET /agents/:id: true when someone else owns the agent and shared it with you.

POST /agents

POST /api/v1/agents
Permission agents_manage

Creates an agent.

Body
name string required
Must not be blank.
ai_model_id uint required
Required in practice. Checked against your workspace’s data-residency policy for agents. The model’s existence is not explicitly checked here.
tag string optional
The @handle. When omitted it is derived from name (lower-cased, other characters replaced by -, max 50, fallback agent).
icon string optional default cpu
Icon name.
color string optional default #F59E0B
Hex color.
description string optional
One-line description.
system_prompt string optional
The job description. Passed through your workspace’s sensitive-data guard.
tools array optional
[{"name", "use"}].
mcp_server_ids uint[] optional
Remote MCP servers to attach. Only servers you own are attached; others are silently ignored.
knowledge_base_ids uint[] optional
Knowledge bases to attach. Only ones you can access are attached; others are silently dropped.
file_ids uint[] optional
Files to attach. Only files you own are attached; others are silently ignored.
knowledge_base_limit_node_ids uint[] optional
Restrict retrieval to these nodes.
world_knowledge bool optional default false
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

GET /api/v1/agents
Permission agents_get MCP tool list_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.

Query parameters
search string query optional
Case-insensitive substring match on name.
shared string query optional
false: only agents you own. true: only agents owned by others and shared with you. Omitted: both.
usage_sort string query optional
true orders by number of messages sent with the agent, descending. Otherwise no explicit order.
page integer query optional default 1
Page number.
size integer query optional default 10
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

GET /api/v1/agents/:id
Permission agents_get MCP tool get_agent

Returns one agent you own or that has been shared with you, fully loaded: ai_model, mcp_servers, knowledge_bases, files and is_shared.

Path parameters
id string path required
Agent ID or slug.

Errors. 404 "Agent not found", 403 "You don't have access to this agent".

PATCH /agents/:id

PATCH /api/v1/agents/:id
Permission agents_manage

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.

Path parameters
id string path required
Agent ID or slug.
Body
name string optional
New name. Not blank.
system_prompt string optional
New system prompt.
ai_model_id uint optional
New model.
mcp_server_ids uint[] optional
Omitted: leave as is. Any array, including [], replaces the whole set.
knowledge_base_ids uint[] optional
Omitted: leave as is. Any array, including [], replaces the whole set.
file_ids uint[] optional
Omitted: leave as is. Any array, including [], replaces the whole set.
tag string optional
As on create.
icon string optional
As on create.
color string optional
As on create.
description string optional
As on create.
tools array optional
As on create.
knowledge_base_limit_node_ids uint[] optional
As on create.
world_knowledge bool 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

DELETE /api/v1/agents/:id
Permission agents_delete

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.

Path parameters
id string 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 sends instructions as 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 marked error with "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"
    }
  ]
}
FieldNotes
instructionsThe prompt sent on every run, after sensitive-data redaction.
enabledA disabled task’s cron triggers do not fire.
triggersOmitted 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_countOnly on GET /workflow-tasks/:id: executions ever recorded.
success_rateOnly on GET /workflow-tasks/:id: share of success among the 30 most recent executions, 0-1 (0 when there are none).

POST /workflow-tasks

POST /api/v1/workflow-tasks

Creates a workflow task.

Body
name string required
Not blank.
ai_model_id uint required
Must exist (400 "AI model not found") and pass the data-residency policy for workflows.
instructions string required
Not blank. Passed through the sensitive-data guard.
description string optional
Free text.
enabled bool optional default true
Whether the task’s triggers fire.
triggers array optional
Trigger requests, see below.
Trigger request
type string required
Must be cron. Anything else gives 400 "unsupported trigger type: <x>".
cron_expression string required
Standard 5-field cron: minute, hour, day of month, month, day of week. Invalid gives 400 "invalid cron_expression: ...".
enabled bool optional default false
Defaults to false. A trigger sent without "enabled": true is saved disabled and never fires.
timezone string optional default UTC
IANA zone such as Europe/Budapest. Invalid gives 400 "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

GET /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.

Query parameters
search string query optional
Case-insensitive substring match on name.
page integer query optional default 1
Page number.
size integer query optional default 10
Items per page.

Response. 200 with an array of Workflow task objects.

GET /workflow-tasks/:id

GET /api/v1/workflow-tasks/:id

Returns one of your workflow tasks with ai_model, triggers, total_run_count and success_rate.

Path parameters
id string 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

PATCH /api/v1/workflow-tasks/:id

Updates one of your workflow tasks. Every field is optional; omitted means unchanged.

Path parameters
id string path required
Workflow task ID or slug.
Body
name string optional
Not blank.
description string optional
Free text.
ai_model_id uint optional
New model.
instructions string optional
Not blank.
enabled bool optional
Switching from false to true recomputes every enabled trigger’s next_trigger_at from now.
triggers array 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

DELETE /api/v1/workflow-tasks/:id

Unschedules and deletes the triggers, and soft-deletes the task and its executions. Owner only.

Path parameters
id string path required
Workflow task ID or slug.

Response. 204 No Content.

POST /workflow-tasks/:id/manual-run

POST /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.

Path parameters
id string 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"])
        break
const 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

GET /api/v1/workflow-tasks/:id/executions

Returns a task’s run history, newest first. Owner only.

Path parameters
id string path required
Workflow task ID or slug.
Query parameters
page integer query optional default 1
Page number.
size integer query optional default 10
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
  }
]