Prompts and runs

Create saved prompts and their variables, fill them in, run them against one or more models, stream the result and share prompts, all through the REST API.

18 min read · Updated 28 September 2026

A saved prompt is a reusable template from your prompt library. Its text contains {{variables}} that you fill in each time you run it. The REST API lets you build prompts from code, run them with fresh values against the model of your choice, and read the results back. Every request runs as the user who owns the API key.

All paths are relative to https://api.stickyprompts.com/api/v1. Request bodies are JSON.

Key concepts

A prompt is a named template. Its text lives in a prompt version: there is one version per prompt, and posting new content overwrites it. A version has variables, referenced in the text as {{slug}}, for example Write a {{tone}} summary of: {{text}}.

To run a prompt you fill in its variables. That creates an instance: an immutable copy of the template with the values substituted. You then run the instance against one or more models. Each run creates a conversation (called conversation_context in the API) that you can keep chatting in through the Conversations endpoints.

prompt -> prompt_version (content with {{slug}} placeholders) -> variables
                    |
                    +-> instance (variable values, parsed content) -> run -> conversation

Shortcut endpoints under /run/prompt/... create the instance and run it in one call. POST /run sends raw text to a model with no prompt at all.

Conventions

  • Pagination. Where noted, send page (default 1) and size (default 10). The response headers X-Page, X-Page-Size and X-Total-Count describe the result. See Errors, pagination and quotas.
  • Project scope. Several endpoints read an optional X-Project-ID: <numeric id> header. A non-numeric value is treated as absent.
  • Field casing. Database objects use capitalised ID, CreatedAt, UpdatedAt and DeletedAt next to snake_case fields. The RunHistory result uses lowercase id and created_at.
  • Rendered text. Run result and thinking fields contain HTML rendered from the model’s Markdown, not the raw Markdown.
  • Errors are {"error": "..."}. Quota failures are 429 {"error":"Quota exceeded","reason":"..."}. Guardrail blocks add a machine code, for example {"error": "...", "code": "SENSITIVE_DATA_BLOCKED"}. Data-residency failures use the codes EU_INFERENCE_REQUIRED, ZERO_RETENTION_REQUIRED, NO_PROVIDER_ASSIGNED and NO_ENDPOINT_AVAILABLE.

The Prompt object

{
  "ID": 42,
  "CreatedAt": "2026-09-20T10:15:00Z",
  "UpdatedAt": "2026-09-20T10:15:00Z",
  "DeletedAt": null,
  "name": "Renewal check-in email",
  "slug": "k3j9x2qa",
  "user_id": 7,
  "project_id": null,
  "shared_with_workspace_id": null,
  "current_version_id": 88,
  "current_version": null,
  "ai_model_id": 12,
  "description": "Friendly renewal reminder for a Northwind customer",
  "is_favorite": false,
  "category_id": 3,
  "category": null,
  "example_output": "",
  "duplicated_from_id": null,
  "file_data": [],
  "tools": [{ "name": "web_search", "use": true }],
  "mcp_servers": [],
  "ai_style": "prompty"
}
  • current_version is {ID, ..., prompt_id, content, variables[], current_instance_id, current_instance} when loaded; most list endpoints leave it null.
  • category is {ID, CreatedAt, UpdatedAt, DeletedAt, name} when loaded.
  • file_data is an array of File objects when loaded, otherwise null. A File is {ID, CreatedAt, UpdatedAt, DeletedAt, name, path, preview_path, public_path, user_id, workspace_id, project_id, fileType, partial}; note the camelCase fileType.
  • tools is [{"name", "use"}] or null. Tool names: web_search, image_generation, video_generation, speech_generation, music_generation, remote_mcp.
  • mcp_servers is an array of remote MCP server IDs.

Prompts

GET /prompt

GET /api/v1/prompt
Permission prompts_get MCP tool list_prompts

Lists your own prompts that are not in a project, newest first. With X-Project-ID, lists that project’s prompts instead.

Query parameters
search string query optional
Matched against name and description.
page integer query optional default 1
Page number.
size integer query optional default 10
Items per page.
X-Project-ID integer header optional
List this project’s prompts.

Response. 200 with an array of Prompt objects (current_version, category and file_data not loaded). With X-Project-ID, each item also has "can_delete": bool, true only when you can manage the project’s prompts and own that prompt.

Errors. 404 {"error":"Project not found"}, 403 {"error":"You don't have access to this project"}.

GET /prompt/all

GET /api/v1/prompt/all
Permission prompts_get

Returns your own prompts, prompts shared with you and prompts you have favorited in one list, ordered by when you last ran each one (falling back to creation time). Prompts inside a project are excluded.

Query parameters
search string query optional
Matched against name and description.
page integer query optional default 1
Page number.
size integer query optional default 10
Items per page.
showWorkspaceShared string query optional
"true" also includes prompts shared with your workspace.

Response. 200 with an array of Prompt objects with file_data loaded, or [].

GET /prompt/last_used

GET /api/v1/prompt/last_used
Permission prompts_get

Returns up to 8 prompts you have run, most recently run first, with file_data loaded. Prompts you never ran are left out. Pagination headers are set, but page and size are ignored.

GET /prompt/most_used

GET /api/v1/prompt/most_used
Permission prompts_get

Returns up to 8 prompts ordered by how often you have run them. No pagination headers.

GET /prompt/recently_added

GET /api/v1/prompt/recently_added
Permission prompts_get

Returns up to 8 of your own prompts, newest first, with file_data loaded.

POST /prompt

POST /api/v1/prompt
Permission prompts_manage

Creates a prompt. It starts without a version: call POST /prompt_version next to give it content. The model comes from your preferences (or the first model in the catalogue), and ai_style from your preferences (or prompty). To change either, call PUT /prompt/:id afterwards.

Body
name string optional
Not validated on create, but send it.
description string optional
Shown on the prompt card.
example_output string optional
Sample output.
category_id uint optional
Prompt category ID, or null.
tools array optional
Default tools, [{"name", "use"}].
mcp_servers uint[] optional
Remote MCP server IDs.
X-Project-ID integer header optional
Create the prompt inside this project. You need permission to manage the project’s prompts.

ai_model_id, ai_style and slug are ignored on create; a random slug is generated.

curl -X POST https://api.stickyprompts.com/api/v1/prompt \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Renewal check-in email", "description": "Friendly renewal reminder" }'

Response. 201 with the Prompt object.

Errors. 400 malformed JSON, 404 {"error":"Project not found"}, 403 {"error":"You don't have access to manage prompts of this project"}, 409 {"error":"Entity already exists"}.

PUT /prompt/:id

PUT /api/v1/prompt/:id
Permission prompts_manage

Replaces a prompt’s editable fields. Only your own prompts.

Path parameters
id integer path required
Numeric prompt ID.
Body
name string required
Must not be blank.
description string optional
Overwritten.
ai_model_id uint optional
Overwritten. Omitting it or sending 0 stores 0.
example_output string optional
Overwritten.
category_id uint optional
Overwritten.
ai_style string optional
Overwritten.
tools array optional
Changed only when present.
mcp_servers uint[] optional
Changed only when present.

All of name, description, ai_model_id, example_output, category_id and ai_style are overwritten, so always send the current values of fields you do not want to change.

Response. 200 with the Prompt object. Errors. 404 {"error":"Prompt not found"}, 400 {"error":"Name is required"}.

POST /prompt_version

POST /api/v1/prompt_version
Permission prompts_manage

Sets a prompt’s template content. The first call creates the version and makes it current; later calls overwrite content on the same version. Create variables with POST /variable, not through this body.

Body
prompt_id uint required
One of your own prompts.
content string required
Template text. Reference variables as {{slug}}.

Response. 201 on both create and update:

{
  "ID": 88, "CreatedAt": "...", "UpdatedAt": "...", "DeletedAt": null,
  "prompt_id": 42,
  "content": "Write a renewal check-in email to {{customer_name}} about their {{plan}} plan.",
  "variables": null,
  "current_instance_id": null,
  "current_instance": null
}

variables is not loaded on the update path. Errors. 400 malformed JSON, 404 {"error":"Prompt not found"}.

DELETE /prompt/:id

DELETE /api/v1/prompt/:id
Permission prompts_delete

Soft-deletes one of your own prompts, and removes its shares and everyone’s favorites of it.

Path parameters
id integer path required
Numeric prompt ID.

Response. 204. 404 with an empty body when the prompt does not exist or is not yours.

POST /prompt/:id/duplicate

POST /api/v1/prompt/:id/duplicate
Permission prompts_manage

Copies a prompt you own or can access (the public catalogue, prompts shared with you, prompts shared with your workspace). The copy includes name, description, model, example output, tools, style, category, version content and variables (name, slug, description, data_type, default_value, required, sort, is_primary, is_multi_select, options). Not copied: mcp_servers, attached files, and each variable’s file_types and default_file_id.

Path parameters
id string path required
Numeric prompt ID or slug.
Headers
X-Project-ID integer header optional
Create the copy in this project, keeping the original name. Without it, " (copy)" is appended to the name.

Response. 200 with the new Prompt object, current_version.variables filled in.

Errors. 404 {"error":"Prompt not found"}, 403 {"error":"You don't have access to this prompt"}, 404 {"error":"Project not found"}, 403 {"error":"You don't have access to manage prompts of this project"}.

PUT /prompt/:id/move-to-project

PUT /api/v1/prompt/:id/move-to-project
Permission prompts_manage

Moves a prompt into a project, between projects, or out of a project. No body. You need manage-prompts permission on every project involved, and a prompt moved into a project must be yours.

Path parameters
id integer path required
Numeric prompt ID.
Headers
X-Project-ID integer header optional
The target project. Leave it out to remove the prompt from its project.

Response. 200 with the Prompt object, project loaded.

Errors. 404 {"error":"Prompt not found"} or {"error":"Prompt not found or you don't have access to it"}, 400 {"error":"Prompt is already not in a project"}, 400 {"error":"Prompt is already in this project"}, 404 {"error":"Project not found"}, 403 without project permissions.

PUT /prompt/:id/share_with_workspace

PUT /api/v1/prompt/:id/share_with_workspace
Permission prompts_manage

Makes one of your own prompts visible to your whole workspace, or private again.

Path parameters
id integer path required
Numeric prompt ID.
Body
shared bool required
true to share with the workspace, false to make it private.

Response. 200 with the Prompt object; shared_with_workspace_id is your workspace ID or null. Errors. 404 {"error":"Prompt not found"}, 400 {"error":"User is not part of a workspace"}.

Favorites

GET /prompt/favorite

GET /api/v1/prompt/favorite
Permission prompts_get

Returns your favorite prompts in the order you favorited them (oldest first). Every item has "is_favorite": true; version, category and files are not loaded. No pagination.

POST /prompt/:id/favorite

POST /api/v1/prompt/:id/favorite
Permission prompts_manage

Marks a prompt as a favorite. It must be your own, shared with you directly, or a public catalogue prompt. A prompt shared only with your workspace returns 403.

Path parameters
id integer path required
Numeric prompt ID.

Response. 201. The nested prompt key really is capitalised Prompt:

{ "ID": 311, "CreatedAt": "2026-09-28T09:00:00Z", "UpdatedAt": "2026-09-28T09:00:00Z", "DeletedAt": null,
  "user_id": 7, "prompt_id": 42, "Prompt": null }

Errors. 404 {"error":"Prompt not found"}, 403 {"error":"You don't have access to this prompt"}, 409 {"error":"This prompt is already marked as favorite"}.

DELETE /prompt/:id/favorite

DELETE /api/v1/prompt/:id/favorite
Permission prompts_manage

Removes a favorite.

Path parameters
id integer path required
Numeric prompt ID.

Response. 204. Errors. 404 {"error":"Prompt not found in favorites"}.

Variables

Variables belong to a prompt version. The slug must match a {{slug}} placeholder in the content and be unique within the version. See Prompt variables for how they behave in the app.

{
  "ID": 301, "CreatedAt": "...", "UpdatedAt": "...", "DeletedAt": null,
  "prompt_version_id": 88,
  "name": "Customer name",
  "slug": "customer_name",
  "description": "Who the email is addressed to",
  "data_type": "short_text",
  "default_value": "",
  "required": true,
  "sort": 0,
  "is_primary": true,
  "is_multi_select": false,
  "options": null,
  "file_types": "",
  "default_file_id": null,
  "default_file": null
}
  • data_type is short_text, long_text, select, number, json or lov.
  • options (for select-style variables) is [{"label": "...", "value": "..."}].
  • is_primary marks the variable that the /run/prompt/last and /run/prompt/:id shortcuts fill in.

POST /variable

POST /api/v1/variable

Adds a variable to a prompt version. The prompt must be yours. The body is the Variable object without ID and timestamps; the server does not validate fields other than these three.

Body
prompt_version_id uint required
The version to add to.
name string required
Display name.
slug string required
Matches {{slug}} in the content.
description string optional
Help text.
data_type string optional
short_text, long_text, select, number, json or lov.
default_value string optional
Used when no value is sent.
required bool optional
Enforced by POST /instance.
sort integer optional
Display order.
is_primary bool optional
The variable the shortcut endpoints fill.
is_multi_select bool optional
Allow several options.
options array optional
[{"label", "value"}].
file_types string optional
Accepted file types.
default_file_id uint optional
Default file.

Response. 201 with the Variable object.

Errors. 400 malformed JSON, 404 {"error":"Prompt version not found"}, 403 {"error":"You are not allowed to add variables to this prompt"}, 409 {"error":"Entity already exists"} (duplicate slug).

PUT /variable/:id

PUT /api/v1/variable/:id

Replaces a variable’s fields. The update overwrites every field, so send them all: name, slug, description, data_type, default_value, required, sort, is_primary, is_multi_select, options, file_types and default_file_id.

Path parameters
id integer path required
Variable ID.

History is preserved: if you change name or slug on a variable that already has stored values from earlier runs, the server first archives the old variable (a soft-deleted copy whose slug gets a _deleted_<timestamp> suffix), moves those values to it, then updates the variable in place. On that path, file_types and default_file_id are not updated.

Response. 200 with the Variable object. Errors. 404 {"error":"Variable not found"}, 403 {"error":"You are not allowed to update variables of this prompt"}.

DELETE /variable/:id

DELETE /api/v1/variable/:id

Deletes a variable.

Path parameters
id integer path required
Variable ID.

Response. 204. Errors. 404 {"error":"Variable not found"}, 403 {"error":"You are not allowed to delete variables of this prompt"}.

Instances

An instance is the template with values filled in. Every {{slug}} is replaced by the value you sent, or by the variable’s default_value when the value is empty or missing. Placeholders that match no variable stay as they are. If your workspace has guardrails, they are applied to the values and the parsed content before anything is stored, so values may come back redacted.

{
  "ID": 5120, "CreatedAt": "...", "UpdatedAt": "...", "DeletedAt": null,
  "prompt_version_id": 88,
  "content": "Write a renewal check-in email to Northwind Traders about their Business plan.",
  "variable_values": [
    { "ID": 9001, "variable_id": 301, "prompt_version_instance_id": 5120, "value": "Northwind Traders", "file_id": null, "file": null }
  ]
}

POST /instance

POST /api/v1/instance
Permission prompts_manage

Fills in a version’s variables and stores the result as a new instance, which also becomes the version’s current instance.

Body
prompt_version_id uint required
The version to fill in.
variable_values array optional
Items {"variable_id": uint, "value": string, "file_id": uint | null}. Required when the version has required variables.
curl -X POST https://api.stickyprompts.com/api/v1/instance \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt_version_id":88,"variable_values":[{"variable_id":301,"value":"Northwind Traders"},{"variable_id":302,"value":"Business"}]}'

Response. 201 with the instance object.

Errors. 404 {"error":"Prompt version not found"}, 400 {"error":"Invalid instance request"} when a required variable has no non-empty value, 400 {"error":"...","code":"SENSITIVE_DATA_BLOCKED"} when a guardrail blocks the input.

GET /instance/:id

GET /api/v1/instance/:id
Permission prompts_get

Returns an instance with its variable_values. 404 with an empty body if it does not exist.

Path parameters
id integer path required
Instance ID.

Running prompts

How the model is chosen

EndpointModel used
POST /instance/:id/run and /run/streamEach ID in ai_model_ids. No default and no "auto" value: send no IDs and nothing runs. With agent_id, or when continuing a conversation that has an agent, the agent’s model replaces ai_model_ids.
POST /run/prompt/last and POST /run/prompt/:idThe model of the run being replayed. When /run/prompt/:id has no earlier run, the prompt’s own ai_model_id.
POST /run/prompt/:id/variablesThe prompt’s ai_model_id.
POST /runai_model_id, or the model whose version (the provider’s model identifier) equals ai_model_name.

None of these use automatic model routing; that belongs to the Conversations endpoints. Every run endpoint checks the chosen model against your workspace’s model access rules (400 {"error":"The selected AI model has been restricted by your organization"}, worded slightly differently on the shortcuts) and data residency rules (400 with a residency code).

The RunHistory object

{
  "id": 7781,
  "created_at": "2026-09-28T09:12:03Z",
  "result": "<p>Hi Northwind team, your Business plan renews on ...</p>\n",
  "raw_text": null,
  "thinking": "",
  "runtime": 4210,
  "ai_model_id": 12,
  "input_prompt": "Write a renewal check-in email to {{customer_name}} about their {{plan}} plan.",
  "variables": [],
  "conversation_context_id": 16044,
  "conversation_context_slug": "q8w2m1zc",
  "conversation_context_name": "",
  "is_interactive": true,
  "input_file_data": [],
  "output_file_data": [],
  "error_text": null,
  "error_id": null,
  "run_status": "success"
}
FieldMeaning
resultThe model’s answer rendered from Markdown to HTML. <think> blocks are stripped.
raw_textAlways null here. For the raw Markdown, read the conversation (GET /conversation/:id) or the stream’s raw_text.
thinkingReasoning rendered as HTML, or "".
runtimeMilliseconds from start to end. 0 while not finished, for example in the streaming response.
input_promptThe unparsed template. The substituted text is the instance’s content.
variables{id, variable_id, name, slug, value} items. Empty on the run endpoints; filled by GET /prompt/:id/run_history.
conversation_context_id, _slug, _nameThe conversation created or continued by this run.
is_interactiveWhether the conversation can be continued. false for comparison-mode runs.
input_file_data, output_file_data{id, name, path} files attached to the user message, and files the model generated.
run_statuspending, then generating, then success or error.
error_text, error_idSet when the provider call failed.

Usage and credits are not part of any run response.

POST /instance/:id/run

POST /api/v1/instance/:id/run
Permission run_prompt

Runs an instance and waits until the model finishes. A new conversation holding the user message (the parsed instance content) and the AI reply is created, unless you pass conversation_context_id. If that conversation has an active model comparison, the instance runs once in each thread and all results are returned.

Path parameters
id integer path required
Instance ID, from POST /instance.
Query parameters
comparison_mode string query optional default false
"true" marks the conversation non-interactive; a restricted model then produces “One of the selected AI models has been restricted by your organization”.
X-Project-ID integer header optional
The project’s files are added to the run’s attachments and the conversation is filed under the project.
Body
ai_model_ids uint[] required
One run per model ID. Required in practice: an empty array runs nothing and returns 200 null. The number of IDs is limited by server configuration (default 1); going over returns 503 {"error":"cannot run this many models in parallel"}. The free tier cannot send more than one: 503 {"error":"free tier does not support comparison mode"}.
ai_style string optional
Response style, for example prompty. Not taken from the prompt automatically, so pass the prompt’s ai_style.
attachments array optional
Items {"id": uint, "type": "file" | "transcription", "temp": bool}. Files with an extension the model does not support are silently skipped.
conversation_context_id uint optional
Continue this conversation instead of creating one.
is_prompt_studio bool optional
Tag the conversation as a Prompt Studio run (source studio). Only studio runs appear in GET /prompt/:id/run_history.
flash_mode bool optional
Forwarded to the model runner.
reasoning_effort string optional
For thinking models, for example low, medium or high. Allowed values depend on the model; unknown values fall back to the model’s default.
knowledge_base_ids uint[] optional
Knowledge bases to retrieve context from. Only bases you may access are used.
knowledge_base_limit_node_ids uint[] optional
Restrict retrieval to these nodes.
world_knowledge bool optional
With knowledge bases, whether the model may also use general knowledge.
agent_id uint optional
Run as this agent. Its model, system prompt, files, tools, MCP servers and knowledge bases override the request fields.
tools array optional
Items {"name", "use", "mcp_tool_id"}. mcp_tool_id is needed only for remote_mcp.
curl -X POST https://api.stickyprompts.com/api/v1/instance/5120/run \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ai_model_ids":[12],"ai_style":"prompty","tools":[{"name":"web_search","use":true}]}'
import os, requests

r = requests.post(
    "https://api.stickyprompts.com/api/v1/instance/5120/run",
    headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
    json={"ai_model_ids": [12], "ai_style": "prompty",
          "tools": [{"name": "web_search", "use": True}]},
    timeout=300,
)
r.raise_for_status()
for run in r.json():
    if run["run_status"] == "error":
        print("Failed:", run["error_text"], run["error_id"])
    else:
        print(run["conversation_context_id"], run["result"])  # result is HTML
const res = await fetch("https://api.stickyprompts.com/api/v1/instance/5120/run", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    ai_model_ids: [12],
    ai_style: "prompty",
    tools: [{ name: "web_search", use: true }],
  }),
});
const runs = await res.json(); // one RunHistory per model
for (const run of runs) console.log(run.run_status, run.error_id ?? run.result);

Response. 200 with an array of RunHistory objects, one per model.

Errors

StatusWhen
400Invalid JSON; restricted model; residency violation (with code); knowledge base not found; invalid project ({"error":"Invalid project ID"}); sensitive-data block (code: SENSITIVE_DATA_BLOCKED); MCP errors ({"error": "...", "error_code": "..."}).
404Instance not found (empty body), {"error":"ConversationContext not found"}, {"error":"Agent not found"}, {"error":"Project not found"}.
429Quota exceeded.
503Other failures before the model call, such as the model limit or the free-tier restriction.

POST /instance/:id/run/stream

POST /api/v1/instance/:id/run/stream
Permission run_prompt

Takes the same path, query, header and body as /run and runs the same validation, but it does not stream over this HTTP response. Instead:

  1. The server creates the runs and the conversation, starts generation in the background, and returns 200 right away with an array of RunHistory objects in run_status: "pending", result: "", runtime: 0, each with a valid conversation_context_id.
  2. Tokens are pushed to the conversation’s WebSocket, GET /conversation/{conversation_context_id}/ws. It takes the same Authorization: Bearer header and needs the conversations_get permission. Server-side clients can send that header; browsers cannot.
  3. Every frame is a JSON text message {"type": ..., "data": ...}:
typedataNotes
messageMessage objectA full snapshot of the AI message so far, not a delta. text is the HTML rendering and raw_text the Markdown; also parts, thinking, citations, status (thinking, generating, finished) and ai_model_id. Replace your copy on each frame.
run_dataRunHistorySent with message updates. result holds the HTML so far and run_status the current status.
ai_message_endnoneGeneration finished successfully.
error_messageMessage objectGeneration failed; see error_text, error_id and error_code. Terminal.
ai_model_change{ai_model_id, ai_style, auto_model_type, reasoning_effort}Conversation settings changed. Not specific to prompt runs.
  1. After ai_message_end, read the final state with GET /conversation/:id.

Events sent before any client connects are buffered and replayed to the first connection, so you can open the socket right after the POST returns. Your own user message is not echoed back. Once the POST has returned 200, provider errors arrive only as error_message frames; errors before generation (model limit, free tier, residency) come back as the non-200 statuses listed for /run. The legacy GET /conversation/:id/stream SSE endpoint does not relay message snapshots for these runs, so use the WebSocket. Full WebSocket details and client examples are in Conversations.

POST /run/prompt/last

POST /api/v1/run/prompt/last
Permission run_prompt

Replays your most recent prompt run with new “primary data”. Built for quick re-runs, for example from a browser extension. It picks the newest run of any prompt you own or that is shared with you, builds a new instance where the primary variable (or the only variable) gets data and every other variable keeps its value from that run (or its default), and runs it with the same model and tools and the prompt’s ai_style. A new interactive conversation is created (source extension).

Body
data string required
New text for the primary variable.

Response. 200 with a single RunHistory object, not an array.

Errors. 404 {"error":"No runs found"}, 400 {"error":"Invalid instance request"} when a required variable ends up empty, 400 for a restricted model or a residency or sensitive-data block, 429 on quota. If the model call fails before a run is recorded, the request fails with 500.

POST /run/prompt/:id

POST /api/v1/run/prompt/:id
Permission run_prompt

The same as /run/prompt/last, but for one prompt. It replays the prompt’s latest run with data in the primary variable. If the prompt was never run and it is your own, a first run is built from the variables’ defaults and the prompt’s ai_model_id. The replayed run is the prompt’s latest run overall, not only yours, so on a shared prompt the non-primary values can come from another user’s latest run.

Path parameters
id integer path required
Numeric prompt ID.
Body
data string required
New text for the primary variable.
curl -X POST https://api.stickyprompts.com/api/v1/run/prompt/42 \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"data":"Northwind Traders"}'

Response. 200 with a single RunHistory object. Errors. 404 {"error":"Prompt not found"}, plus the errors of /run/prompt/last.

POST /run/prompt/:id/variables

POST /api/v1/run/prompt/:id/variables
Permission run_prompt MCP tool run_prompt

Runs a prompt’s current version with a full set of variable values: instance creation and the run in one call. Uses the prompt’s ai_model_id and ai_style. The new instance becomes the version’s current instance, and a new interactive conversation is created (source extension).

Path parameters
id integer path required
Numeric prompt ID. The prompt must be yours or shared with you.
Body
variable_values array optional
Items {"variable_id": uint, "value": string, "file_id": uint | null}. Omitted or empty values fall back to the default. required is not enforced here.
web_context string optional
Extra text placed before the parsed prompt, separated by a blank line. Guardrails apply to it.
tools array optional
Same format as in POST /instance/:id/run.
curl -X POST https://api.stickyprompts.com/api/v1/run/prompt/42/variables \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"variable_values":[{"variable_id":301,"value":"Northwind Traders"},{"variable_id":302,"value":"Business"}]}'
import os, requests

r = requests.post(
    "https://api.stickyprompts.com/api/v1/run/prompt/42/variables",
    headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
    json={"variable_values": [
        {"variable_id": 301, "value": "Northwind Traders"},
        {"variable_id": 302, "value": "Business"},
    ]},
    timeout=300,
)
run = r.json()
print(run["run_status"], run["result"])
const run = await fetch("https://api.stickyprompts.com/api/v1/run/prompt/42/variables", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    variable_values: [
      { variable_id: 301, value: "Northwind Traders" },
      { variable_id: 302, value: "Business" },
    ],
  }),
}).then((r) => r.json());
console.log(run.run_status, run.result);

Response. 200 with a single RunHistory object.

Errors. 404 {"error":"Prompt not found"}, 400 for a restricted model or a residency or sensitive-data block, 429 on quota, 503 {"error":"..."} on a run failure.

The MCP tool run_prompt does the same job with a map of variable slugs to values; it returns only the text and does not create an interactive conversation.

POST /run

POST /api/v1/run
Permission custom_run MCP tool custom_run

Sends free text to a model and returns the answer, with no prompt, no variables and no conversation. The call is synchronous. No system prompt, workspace prompt or project instructions are added, the style is fixed to prompty, and nothing is saved as a conversation. Usage is still recorded.

Body
prompt string required
The text sent as the single user message.
ai_model_id uint optional
Model ID. Send this or ai_model_name.
ai_model_name string optional
Matched against the model’s version field, the provider-level model identifier. Send this or ai_model_id.
curl -X POST https://api.stickyprompts.com/api/v1/run \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Suggest a subject line for a Northwind renewal email.","ai_model_id":12}'
import os, requests

r = requests.post(
    "https://api.stickyprompts.com/api/v1/run",
    headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
    json={"prompt": "Suggest a subject line for a Northwind renewal email.", "ai_model_id": 12},
    timeout=300,
)
r.raise_for_status()
print(r.json()["text"])  # raw Markdown
const msg = await fetch("https://api.stickyprompts.com/api/v1/run", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "Suggest a subject line for a Northwind renewal email.", ai_model_id: 12 }),
}).then((r) => r.json());
console.log(msg.text); // raw Markdown

Response. 200 with a Message object that is not saved: its ID is 0 and its timestamps are zero. text holds the model’s raw Markdown, unlike the run endpoints, which return HTML.

{
  "ID": 0, "CreatedAt": "0001-01-01T00:00:00Z", "UpdatedAt": "0001-01-01T00:00:00Z", "DeletedAt": null,
  "conversation_context_id": null,
  "ai_model_id": 12,
  "text": "Your Northwind plan renews soon - here is what is new",
  "isUser": false,
  "error_text": null, "error_id": null,
  "ai_style": "prompty",
  "status": "finished",
  "type": "normal"
}

Errors. 404 {"error":"AI model not found"}, 400 for a restricted model or a residency or sensitive-data block, 429 on quota, 500 {"error":"..."} when the model call fails (this endpoint uses 500, not 503).

GET /prompt/:id/run_history

GET /api/v1/prompt/:id/run_history
Permission prompts_get

Returns your Prompt Studio run history for a prompt, newest conversation first. Only conversations you started with is_prompt_studio: true are included.

Path parameters
id integer path required
Numeric prompt ID. The prompt must be yours, shared with you, or a catalogue prompt.
Query parameters
page integer query optional default 1
Page number. The pagination headers count conversations.
size integer query optional default 10
Conversations per page.

Response. 200 with an array of arrays of RunHistory objects: one inner array per conversation, with several items when that run compared models. variables is filled in here. [] when there is nothing.

Prompt files

Files attached to the prompt itself. OpenAI-protocol models use them through file search; Together models get them as an extra user message; for other providers, how they are used is not documented. Upload files first with POST /file.

POST /prompt/:id/file

POST /api/v1/prompt/:id/file
Permission prompts_manage

Links uploaded files to a prompt. Files linked earlier stay linked.

Path parameters
id integer path required
Numeric prompt ID.
Body
file_ids uint[] required
IDs of uploaded files.

Response. 200 with the Prompt object; file_data lists only the files added in this call.

Errors. 400 {"error":"Invalid request body"}, 400 {"error":"Invalid prompt ID"}, 404 {"error":"Prompt not found"}, 400 {"error":"File not found"}.

GET /prompt/:id/file

GET /api/v1/prompt/:id/file
Permission prompts_get

Returns an array of File objects linked to the prompt. 400 if id is not numeric.

Path parameters
id integer path required
Numeric prompt ID.

DELETE /prompt/:id/file/:fileId

DELETE /api/v1/prompt/:id/file/:fileId
Permission prompts_manage

Unlinks a file from one of your own prompts. If no other prompt or message uses the file, the stored file is deleted too.

Path parameters
id integer path required
Numeric prompt ID.
fileId integer path required
File ID.

Response. 204. Errors. 404 {"error":"Prompt not found"}, 403 {"error":"You do not have permission to delete this file from the prompt"}, 404 {"error":"File not found"}, 502 {"error":"Failed to delete file"}.

Sharing with individual users

GET /share

GET /api/v1/share
Permission prompts_get

Returns prompts shared with you, newest first.

Query parameters
scope string query optional default all
user (shared directly with you), workspace (shared with your workspace) or all.
search string query optional
Filter by text.
page integer query optional default 1
Page number.
size integer query optional default 10
Items per page.

Response. 200 with an array of Prompt objects, each with a shared_by object. scope=workspace without a workspace returns []; an empty result in the other scopes is null, not [].

[{ "ID": 42, "name": "Renewal check-in email", "...": "...",
   "shared_by": { "email": "john.smith@northwind.example", "first_name": "John", "last_name": "Smith" } }]

Errors. 400 {"error":"Invalid scope parameter"}.

GET /prompt/:id/share

GET /api/v1/prompt/:id/share
Permission prompts_get

Lists the users a prompt is shared with. Each Share has a full user object.

Path parameters
id integer path required
Numeric prompt ID.
[{ "ID": 55, "CreatedAt": "...", "UpdatedAt": "...", "DeletedAt": null,
   "user_id": 19, "user": { "ID": 19, "email": "anna@northwind.example", "first_name": "Anna", "status": "active", "...": "..." },
   "prompt_id": 42, "read_only": true }]

POST /share/:id

POST /api/v1/share/:id
Permission prompts_manage

Shares prompt :id read-only with a user, by email. If no account exists for that email, the server creates an invited account and sends an invitation email; an already-invited user gets the invitation again.

Path parameters
id integer path required
Prompt ID.
Body
email string required
The recipient’s email address.

Response. 200 with an empty body. Errors. 404 {"error":"prompt not found"}, 400 {"error":"invalid email format"}, 400 {"error":"share already exists"}, 400 {"error":"..."} when the invitation fails.

POST /share/:id/resend

POST /api/v1/share/:id/resend
Permission prompts_manage

Sends the invitation email again to a user who has not registered yet.

Path parameters
id integer path required
The prompt ID.
Body
email string required
The invited address.

Response. 204. Errors. 400 {"error":"invalid email format"}, 404 {"error":"prompt not found"}, 404 {"error":"user not found"}, 400 {"error":"user not invited or already registered"}, 404 {"error":"share not found"}.

DELETE /share/:id

DELETE /api/v1/share/:id
Permission prompts_manage

Removes a share and cancels any pending invitations of that user.

Path parameters
id integer path required
The share ID (the ID from GET /prompt/:id/share), not the prompt ID.

Response. 204. Errors. 404 {"error":"share not found"}.