Quickstart
Create an API key, list the models you can use, ask a model a question, hold a conversation and run a saved prompt. About five minutes.
This page takes you from zero to working calls against the StickyPrompts REST API. Every example comes in cURL, Python (requests) and JavaScript (fetch, Node 18 or later). The same key also works for the MCP server, so once you have it you can connect Claude Code or Cursor too.
All REST paths are relative to https://api.stickyprompts.com/api/v1.
1. Create an API key
- Open Settings > API keys
In the app, go to Settings and select API keys. The page shows both base URLs and the exact header to send.
- Generate a key with the permissions you need
Select Generate key, give the key a name (for example Quickstart) and tick its permissions. For this page you need:
ai_models_get- list modelscustom_run- ask a model directlyconversations_manage- start and continue conversationsprompts_getandrun_prompt- find and run saved prompts
- Copy the key now
The full key (
STICKY-API-followed by 64 characters) is shown once. Copy it straight into your secret store. If you lose it, delete the key and generate a new one.
2. Store it as an environment variable
Keep the key out of your code. Every example below reads it from STICKY_API_KEY.
export STICKY_API_KEY="STICKY-API-..."
Every request sends it as a bearer token:
Authorization: Bearer STICKY-API-...
3. List the models you can use
/api/v1/ai_model Models are identified by their numeric ID, which other endpoints take as ai_model_id. Add legacy=false to hide deprecated models, and use models whose selectable is true - false means your workspace’s model access or data-residency rules block it for you.
curl "https://api.stickyprompts.com/api/v1/ai_model?legacy=false" \
-H "Authorization: Bearer $STICKY_API_KEY"import os
import requests
BASE = "https://api.stickyprompts.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"}
r = requests.get(f"{BASE}/ai_model", params={"legacy": "false"}, headers=HEADERS)
r.raise_for_status()
for m in r.json():
if m["selectable"]:
print(m["ID"], m["name"], m["powered_by"])const BASE = "https://api.stickyprompts.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.STICKY_API_KEY}` };
const res = await fetch(`${BASE}/ai_model?legacy=false`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
for (const m of await res.json()) {
if (m.selectable) console.log(m.ID, m.name, m.powered_by);
}The response is a JSON array, trimmed here to the most useful fields (values are illustrative):
[
{
"ID": 42,
"name": "Claude Sonnet 5",
"powered_by": "Anthropic",
"description": "Balanced model for everyday work.",
"flagship": true,
"deprecated": false,
"thinking_model": true,
"reasoning_efforts": ["low", "medium", "high"],
"tools": ["file_upload", "web_search", "remote_mcp"],
"multiplier": "1",
"stream_support": true,
"selectable": true
}
]
Note the mixed casing: ID is capitalised, everything else is snake_case. Decimal values such as multiplier arrive as strings. See Errors, pagination and quotas.
4. Ask a model a question
/api/v1/run POST /run sends one piece of text to a model and returns its answer. Nothing is saved as a conversation, and no system prompt, workspace prompt or project instructions are added. Usage is still recorded.
curl -X POST https://api.stickyprompts.com/api/v1/run \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Write a one-line tagline for Northwind Analytics.", "ai_model_id": 42}'r = requests.post(
f"{BASE}/run",
headers=HEADERS,
json={"prompt": "Write a one-line tagline for Northwind Analytics.", "ai_model_id": 42},
)
r.raise_for_status()
print(r.json()["text"])const res = await fetch(`${BASE}/run`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
prompt: "Write a one-line tagline for Northwind Analytics.",
ai_model_id: 42,
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log((await res.json()).text);The response is an unsaved message object (its ID is 0). The answer is in text, as raw Markdown:
{
"ID": 0,
"ai_model_id": 42,
"text": "Northwind Analytics: see what sells before it sells out.",
"isUser": false,
"status": "finished",
"ai_style": "prompty",
"error_text": null,
"error_id": null
}
5. Start a conversation and send a follow-up
/api/v1/conversation A conversation is saved and appears in your Chat History in the app, just like a chat you started there. POST /conversation creates it, sends the first message and waits for the full reply. Always pass a model: either ai_model_id, or auto_model_type (best, balanced or fastest) to let StickyPrompts pick the best model for each message.
curl -X POST https://api.stickyprompts.com/api/v1/conversation \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Draft three subject lines for the Northwind Q4 newsletter.",
"ai_model_id": 42
}'r = requests.post(
f"{BASE}/conversation",
headers=HEADERS,
json={
"message": "Draft three subject lines for the Northwind Q4 newsletter.",
"ai_model_id": 42,
},
)
r.raise_for_status()
reply = r.json()
conversation_id = reply["conversation_context_id"]
print(reply["raw_text"])const res = await fetch(`${BASE}/conversation`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
message: "Draft three subject lines for the Northwind Q4 newsletter.",
ai_model_id: 42,
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const reply = await res.json();
const conversationId = reply.conversation_context_id;
console.log(reply.raw_text);The response is the finished AI message. Its conversation_context_id is the new conversation’s ID (trimmed):
{
"ID": 90412,
"CreatedAt": "2026-09-28T09:14:03.512Z",
"conversation_context_id": 5120,
"ai_model_id": 42,
"is_auto_model": false,
"text": "Here are three options for the subject line: ...",
"raw_text": "Here are three options for the subject line: ...",
"isUser": false,
"status": "finished",
"error_text": null,
"error_id": null,
"response_duration_ms": 6210,
"attachments": []
}
Send a follow-up to the same conversation with POST /conversation/{id}. Follow-ups take only the message (and optional attachments, knowledge bases and tools); the conversation keeps its model.
/api/v1/conversation/:id curl -X POST https://api.stickyprompts.com/api/v1/conversation/5120 \
-H "Authorization: Bearer $STICKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Make the second one shorter and more playful."}'r = requests.post(
f"{BASE}/conversation/{conversation_id}",
headers=HEADERS,
json={"message": "Make the second one shorter and more playful."},
)
r.raise_for_status()
print(r.json()["raw_text"])const res2 = await fetch(`${BASE}/conversation/${conversationId}`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ message: "Make the second one shorter and more playful." }),
});
if (!res2.ok) throw new Error(`${res2.status} ${await res2.text()}`);
console.log((await res2.json()).raw_text);The reply has the same message shape. For long answers you can use POST /conversation/stream instead, which returns the conversation ID immediately and delivers the reply over a WebSocket - see Conversations and messages.
6. Run a saved prompt with variables
/api/v1/run/prompt/:id/variables Saved prompts contain {{variables}}. This endpoint fills them in and runs the prompt in one call, using the prompt’s own model and response style. It creates a new conversation.
You need two kinds of ID:
- The prompt ID. List your prompts with
GET /prompt(permissionprompts_get) and read each prompt’sID. - The variable IDs. Each item in
variable_valuesnames a variable by its numericvariable_id. An API key cannot read a single prompt with its variables (GET /prompt/:idis not open to keys), so take the IDs from one of these: the prompt’s run history (GET /prompt/:id/run_historylists each run’s variables withvariable_id,slugandvalue), an instance (GET /instance/:id), or the response ofPOST /variableif you created the variable through the API. Look them up once and keep them in your config. See Prompts and runs.
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": "Bluebird Market"},
{"variable_id": 302, "value": "Growth plan renewal"}
]
}'r = requests.post(
f"{BASE}/run/prompt/42/variables",
headers=HEADERS,
json={
"variable_values": [
{"variable_id": 301, "value": "Bluebird Market"},
{"variable_id": 302, "value": "Growth plan renewal"},
]
},
)
r.raise_for_status()
run = r.json()
print(run["run_status"], run["result"])const res3 = await fetch(`${BASE}/run/prompt/42/variables`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
variable_values: [
{ variable_id: 301, value: "Bluebird Market" },
{ variable_id: 302, value: "Growth plan renewal" },
],
}),
});
if (!res3.ok) throw new Error(`${res3.status} ${await res3.text()}`);
const run = await res3.json();
console.log(run.run_status, run.result);Variables you leave out, or send empty, fall back to their default values. The response is a single run object (trimmed):
{
"id": 7781,
"created_at": "2026-09-28T09:12:03Z",
"result": "<p>Hi Sarah, it has been a great year with Bluebird Market ...</p>\n",
"raw_text": null,
"runtime": 4210,
"ai_model_id": 42,
"conversation_context_id": 16044,
"conversation_context_slug": "q8w2m1zc",
"error_text": null,
"error_id": null,
"run_status": "success"
}
Two things to know:
resultis HTML rendered from the model’s Markdown. For the raw Markdown, read the conversation withGET /conversation/16044.- A provider failure still returns
200, withrun_status: "error"anderror_text/error_idset. Checkrun_status. - If you only need to change one input,
POST /run/prompt/:idwith{"data": "..."}replays the prompt’s latest run with new text in its primary variable - no variable IDs needed.
Next steps
Give each key only what its integration needs.
Errors, pagination and quotasHandle 401, 403 and 429, and page through lists.
Connect an MCP clientUse the same key from Claude Code, Cursor or VS Code.
Conversations and messagesStreaming, attachments, knowledge bases and agents in chats.