API Reference
Prompts API

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

MethodPathDescription
POST/api/promptsCreate a prompt
GET/api/promptsList 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}/defaultSet 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.

StatusReason
400Missing workspace_id / name / system_instruction / user_template, or tone not one of the five valid values
403Caller is not a member of workspace_id

List

GET /api/prompts?workspaceId=ws_123
⚠️

This 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 }
StatusReason
400Missing workspaceId
403Caller is not a member of workspaceId

Get

GET /api/prompts/{id}?workspaceId=ws_123

Unlike 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).

StatusReason
400Missing workspaceId
403Caller is not a member of workspaceId
404No 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" }
FieldBehavior
name, system_instruction, user_templatecoerced to string, stripped, replaces the field
tonestripped, validated against the 5-value enum (400 if invalid)
default_audience, default_languagereplace 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).

StatusReason
400Missing workspace_id, or tone present but invalid
403Caller is not a member of workspace_id, or is a member but not the creator
404Prompt not found

Delete

DELETE /api/prompts/{id}?workspaceId=ws_123

Creator-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.

StatusReason
400Missing workspaceId
403Caller is not a member of workspaceId, or is a member but not the creator
404Prompt not found
409This is the caller's last remaining prompt in the workspace

Set default

PUT /api/prompts/{id}/default

Creator-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, "...": "..." }
StatusReason
400Missing workspace_id
403Caller is not a member of workspace_id, or is a member but not the creator
404Prompt not found