Prompts API
Full REST reference for the prompts domain. All routes are under
/api/prompts, require a Cognito JWT (Authorization: Bearer <token>), and
are backed by services/backend/lambda/prompts_handler.py /
prompts_service.py. See specs/api/prompts.md in the repo for the
underlying spec this page is based on, and the
Prompts feature guide for product-level behavior.
Request bodies are snake_case per lk-wiz convention (workspace_id,
system_instruction, ...). Query parameters on this domain are camelCase
(workspaceId) — the standard lk-wiz split (bodies snake_case, query
params camelCase).
Endpoints at a glance
| Method | Path | Description |
|---|---|---|
POST | /api/prompts | Create a prompt |
GET | /api/prompts | List your prompts |
GET | /api/prompts/{id} | Get a prompt |
PUT | /api/prompts/{id} | Update a prompt |
DELETE | /api/prompts/{id} | Delete a prompt |
PUT | /api/prompts/{id}/default | Set as default |
No role-based restriction
Creating a prompt requires only workspace membership — there is no
workspace-role check in prompts_service. specs/SPECS.md §7.8's
authorization matrix lists prompt creation as "Editor and above only," but
the implementation does not enforce that: a viewer-role member can create
a prompt today. The only access restriction that is enforced is
creator-only for update/delete/set-default (below).
Create
POST /api/prompts// Request
{
"workspace_id": "ws_123",
"name": "Professional Thought Leader",
"system_instruction": "You are a professional LinkedIn writer.",
"user_template": "Write about: {idea_title}\n\n{examples}",
"tone": "professional",
"default_audience": "Developers",
"default_language": "English"
}// 201 Response
{
"prompt_id": "a1b2c3d4-...",
"workspace_id": "ws_123",
"name": "Professional Thought Leader",
"system_instruction": "You are a professional LinkedIn writer.",
"user_template": "Write about: {idea_title}\n\n{examples}",
"tone": "professional",
"default_audience": "Developers",
"default_language": "English",
"is_default": false,
"created_by": "user_111",
"created_at": "2026-07-17T12:00:00+00:00",
"updated_at": "2026-07-17T12:00:00+00:00"
}workspace_id, name, system_instruction, and user_template are
required (400 if missing or empty after trimming). tone must be one of
professional, casual, provocative, educational, storytelling
(400 otherwise, error message lists the valid set alphabetically).
is_default is always false on create, even for your first prompt in a
workspace — there is no auto-default-on-first-create behavior. Call
Set as default explicitly.
| Status | Reason |
|---|---|
| 400 | Missing workspace_id / name / system_instruction / user_template, or tone not one of the five valid values |
| 403 | Caller is not a member of workspace_id |
List
GET /api/prompts?workspaceId=ws_123This returns only prompts the caller created in that workspace — not
every prompt in the workspace. Internally it queries the caller's own
USER#{user_id} GSI partition and filters to the requested workspace.
Prompts created by teammates in the same workspace won't appear here (but
are still individually fetchable via Get).
// 200 Response
{ "items": [ /* prompt objects, same shape as Create's response */ ], "count": 2 }| Status | Reason |
|---|---|
| 400 | Missing workspaceId |
| 403 | Caller is not a member of workspaceId |
Get
GET /api/prompts/{id}?workspaceId=ws_123Unlike List, this is not restricted to prompts you created — any workspace member can fetch any prompt in the workspace by id.
Returns the prompt object (same shape as Create's response).
| Status | Reason |
|---|---|
| 400 | Missing workspaceId |
| 403 | Caller is not a member of workspaceId |
| 404 | No prompt {id} in that workspace |
Update
PUT /api/prompts/{id}Partial update — send only the fields you want to change, plus
workspace_id (required for the membership check).
Creator-only. Even a workspace owner gets 403 if they didn't
create this specific prompt.
// Request — rename and switch tone
{ "workspace_id": "ws_123", "name": "Punchy Thought Leader", "tone": "provocative" }| Field | Behavior |
|---|---|
name, system_instruction, user_template | coerced to string, stripped, replaces the field |
tone | stripped, validated against the 5-value enum (400 if invalid) |
default_audience, default_language | replace as-is, no validation — send null to clear |
updated_at is always refreshed, even on an otherwise-empty body. Omitting
a field leaves it unchanged (there's no way to clear text fields except by
sending an explicit empty value).
| Status | Reason |
|---|---|
| 400 | Missing workspace_id, or tone present but invalid |
| 403 | Caller is not a member of workspace_id, or is a member but not the creator |
| 404 | Prompt not found |
Delete
DELETE /api/prompts/{id}?workspaceId=ws_123Creator-only, same rule as Update.
The "can't delete the last prompt" guard counts only your own prompts in the workspace, not the workspace's total prompt count. You can be blocked from deleting your only prompt even while teammates have several of their own in the same workspace — and conversely, you may delete your last prompt in a workspace that still has prompts from other creators.
// 200 Response
{ "deleted": true }Note this endpoint returns 200, not 204 — the body is not empty.
| Status | Reason |
|---|---|
| 400 | Missing workspaceId |
| 403 | Caller is not a member of workspaceId, or is a member but not the creator |
| 404 | Prompt not found |
| 409 | This is the caller's last remaining prompt in the workspace |
Set default
PUT /api/prompts/{id}/defaultCreator-only, same rule as Update. Sets is_default: true
on this prompt and is_default: false on any other prompt you
created in the same workspace.
Default-ness is scoped per (you, workspace) — this call never reads or changes any other user's default prompt, even in the same workspace.
// Request
{ "workspace_id": "ws_123" }// 200 Response — is_default now true, updated_at refreshed
{ "prompt_id": "a1b2c3d4-...", "is_default": true, "...": "..." }| Status | Reason |
|---|---|
| 400 | Missing workspace_id |
| 403 | Caller is not a member of workspace_id, or is a member but not the creator |
| 404 | Prompt not found |