Prompt Manager can expose its store over a small local REST API so that agents and tools can read and edit your prompts with full parity to the app — create, edit, delete, search, and browse folders.
The server is off by default. Enable it in Settings → API Server…, where you
also generate the API key, pick the port, and see a live curl example.
- Open Settings → API Server… in Prompt Manager.
- Tick Enable API server, choose a port (default
8770), click Generate to mint a key, then Save. - Point your tool/agent at the base URL and send the key on every request.
# Health check (no auth)
curl http://127.0.0.1:8770/api/health
# List / search prompts
curl -H "Authorization: Bearer YOUR_KEY" \
"http://127.0.0.1:8770/api/prompts?q=email"| Base URL | http://127.0.0.1:<port>/api (default port 8770) |
| Binding | Loopback only (127.0.0.1). Not reachable from the LAN. |
| Auth | Authorization: Bearer <key> or X-API-Key: <key> on every request (except /health). |
| Content type | Request and response bodies are JSON (application/json). |
| CORS | Allowed for all origins, so browser-based agents can connect. |
Missing/incorrect key → 401. The key is stored locally via QSettings
(~/.config/PromptManager/Prompt Manager.conf on Linux). Regenerating a key in the
dialog immediately invalidates the old one once you Save.
A prompt is:
{
"id": "1a5a9711-67f6-4a28-8f9e-baac949a0070",
"title": "Greeting",
"body": "Hello!",
"folderPath": "General/Sub",
"created": "2026-07-24T15:05:51",
"modified": "2026-07-24T15:05:51"
}id— UUID, assigned by the server on create. Immutable.folderPath—/-separated folder names, e.g.Work/Email. Missing folders in the path are created automatically. Empty/omitted → defaults toGeneral.created/modified— ISO-8601, managed by the server.
Every field this API accepts is a JSON string, and bodies are checked against
the field list each endpoint accepts. Three ways a body is rejected, all with
400, all naming the offender — none of them are silently ignored:
{ "error": "Unknown field 'content' (did you mean 'body'?). Allowed fields: title, body, folderPath",
"status": 400 }
{ "error": "Field 'body' must be a string, not a number", "status": 400 }
{ "error": "Request body must be a JSON object", "status": 400 }All three used to look like success. A misnamed field was dropped and the server
returned 200 echoing the unchanged prompt. A wrong-typed one was worse:
{"body": 123} passed straight through to a string conversion that yields "",
so it blanked the prompt and reported 200. A body that was valid JSON but
not an object (an array, say) parsed fine and then did nothing. In every case the
only way to find out was to read the store back.
Note on folders: folders are derived from prompt
folderPaths. An empty folder (created via the API or the app but containing no prompts) exists only in the running session — it is not persisted across restarts until it holds a prompt.
Liveness probe. No auth required.
{ "status": "ok", "service": "prompt-manager", "version": "2.7.1" }List or search prompts.
| Query param | Description |
|---|---|
folder |
Exact folderPath to filter by (optional). |
q |
Case-insensitive substring; matches title, body, or folderPath (optional). |
curl -H "Authorization: Bearer YOUR_KEY" \
"http://127.0.0.1:8770/api/prompts?folder=Work/Email&q=invoice"{ "count": 1, "prompts": [ { "id": "…", "title": "…", … } ] }A folder that matches nothing returns {"count": 0} rather than 404. That is
deliberate, not an oversight: folders are derived from prompt folderPaths, so a
folder holding no prompts and a folder that doesn't exist are the same state —
there is nothing to tell apart. Use GET /folders if you need to know which
folder paths the running session currently knows about.
Fetch a single prompt (full body included).
curl -H "X-API-Key: YOUR_KEY" \
http://127.0.0.1:8770/api/prompts/1a5a9711-67f6-4a28-8f9e-baac949a0070404 if the id is unknown.
Create a prompt. Returns 201 with the created object.
| Field | Required | Notes |
|---|---|---|
title |
yes | Non-empty. |
body |
no | Defaults to empty string. |
folderPath |
no | Defaults to General; nested folders auto-created. |
Any other field — or any non-string value — is a 400 (see Request bodies are
validated above).
curl -X POST -H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Greeting","body":"Hello!","folderPath":"General"}' \
http://127.0.0.1:8770/api/promptsPartial update — send only the fields you want to change.
| Field | Notes |
|---|---|
title |
If present, must be non-empty. |
body |
Replaces the body. |
folderPath |
Moves the prompt (nested folders auto-created). |
id, created, modified |
Accepted but server-managed and ignored, so you can GET a prompt and PUT the whole object back. A mismatched id is a 400. |
Any other field — or any non-string value — is a 400 (see Request bodies are
validated above).
If none of the supplied values actually differ from what's stored, the update is
a no-op: you get 200 with the prompt unchanged, modified is not
touched, and nothing is written to disk. (modified drives the app's
Newest/Oldest folder sort, so a redundant write would silently reorder the tree.)
curl -X PUT -H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Updated text","folderPath":"Work/Email"}' \
http://127.0.0.1:8770/api/prompts/1a5a9711-…Returns 200 with the updated object; 404 if unknown; 400 on an empty title.
Delete a prompt.
curl -X DELETE -H "Authorization: Bearer YOUR_KEY" \
http://127.0.0.1:8770/api/prompts/1a5a9711-…{ "deleted": "1a5a9711-…" }List all folders as flat paths.
{
"count": 2,
"folders": [
{ "name": "Work", "path": "Work" },
{ "name": "Email", "path": "Work/Email" }
]
}Create a folder path (nested folders auto-created).
curl -X POST -H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"path":"Work/Email"}' \
http://127.0.0.1:8770/api/foldersReturns 201:
{
"path": "Work/Email",
"note": "Empty folders persist across restarts only once they contain a prompt."
}(See the empty-folder persistence note above.)
Delete a folder and every prompt inside it (recursive). Irreversible.
Without confirm=true this is a dry run. It changes nothing and returns 400
telling you exactly what the real call would destroy — so you can look before you
leap, and a mistyped path costs nothing:
curl -X DELETE -H "X-API-Key: YOUR_KEY" \
"http://127.0.0.1:8770/api/folders?path=Work/Email"{
"error": "Refusing to delete 'Work/Email' without confirmation: this would remove 3 prompt(s) and 1 nested folder(s), irreversibly. Retry with &confirm=true if that is what you want.",
"status": 400,
"path": "Work/Email",
"wouldRemovePrompts": 3,
"wouldRemoveFolders": 1,
"dryRun": true
}Add &confirm=true to actually delete:
curl -X DELETE -H "X-API-Key: YOUR_KEY" \
"http://127.0.0.1:8770/api/folders?path=Work/Email&confirm=true"{ "deleted": "Work/Email", "promptsRemoved": 3 }A path that doesn't resolve is still a 404, checked before the guard — so a dry
run also tells you the folder exists.
| Code | Meaning |
|---|---|
200 |
OK |
201 |
Created |
400 |
Bad request: missing, unknown, or wrong-typed field; a body that isn't a JSON object; or malformed JSON |
401 |
Missing or invalid API key |
404 |
Unknown endpoint or resource |
405 |
Method not allowed on that resource |
Error bodies look like:
{ "error": "Prompt not found", "status": 404 }- Discover, then act. Call
GET /foldersandGET /prompts?q=…to orient yourself before creating or editing anything. - Changes are live — the desktop UI updates immediately and the store is saved to disk on every mutation.
- Use
folderPathto keep prompts organized; you don't need to pre-create folders, but you can withPOST /folders. DELETE /foldersneeds&confirm=true. Without it you get a400dry run reporting how many prompts and nested folders the real call would destroy. Use the dry run first; it is free and it is the only preview you get, because there is no undo.- Check the status code, not your HTTP client's exit code. The prompt text
field is
body, notcontent, and every field is a string. Since 2.5.1 a wrong field name is a loud400, and since 2.5.2 so is a wrong type — but you still have to read the code to see it, becausecurlexits0on a400. Usecurl -f, or parse theerrorkey.