Back to docs

Saved prompt library

Version and render reusable prompts with variables

Saved prompt library

Save the prompts you reuse the most, version them, render them with variables, and reference them from anywhere in your application without redeploying client code every time you tweak the wording.

Quickstart

# 1. Save a prompt with two variables
curl -X POST https://app.privaterouter.com/api/prompts \
  -H "Authorization: Bearer $PR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summarise email",
    "description": "Concise 3-bullet summary of an inbound message",
    "content": "Summarise the following email from {{ sender }} in 3 bullet points. Tone: {{ tone }}.\n\n---\n{{ body }}",
    "variables": [
      {"name": "sender",  "description": "who sent the email"},
      {"name": "tone",    "default": "professional"},
      {"name": "body",    "description": "raw email body"}
    ]
  }'
# → 201, returns {id, slug:"summarise-email", active_version:{version:1, ...}}

# 2. Render it with variables
curl -X POST https://app.privaterouter.com/api/prompts/summarise-email/render \
  -H "Authorization: Bearer $PR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {
      "sender": "alice@example.com",
      "body":   "Lorem ipsum..."
    }
  }'
# → 200 {"rendered_content": "Summarise the following email from alice@example.com...",
#         "version_used": 1}

Why use this

  • Decouple prompts from code. Iterate on wording in the dashboard; your application code keeps referencing the same slug.
  • Version history. Every change is a new immutable version. Roll back instantly by activating an older one.
  • Variables. Mark the spots that change per-call with {{ variable }} placeholders. Declare defaults so callers can omit them when they want.
  • Per-user namespace. Slugs are unique within your account. There is no global registry.

{{ variable }} syntax

Placeholders use a single {{ name }} form. Whitespace inside the braces is optional. Names must match ^[A-Za-z_][A-Za-z0-9_]*$.

Hello {{ name }}, your balance is {{balance}}.

Render-time rules:

  • Values supplied in the render request take priority.
  • If a value is missing, the variable's declared default is used.
  • If neither is available, the render fails with 400 and lists every missing required variable.

Versioning model

  • Creating a prompt also creates version 1, which is set active.
  • POST /api/prompts/{id}/versions appends a new version but does not auto-promote it. Drafts stay invisible until you flip the active pointer.
  • POST /api/prompts/{id}/versions/{vid}/activate flips the active pointer to a specific version.
  • Versions are append-only history. There is no edit-in-place.

Routes

MethodPathPurpose
GET/api/promptsList your prompts (with active version)
POST/api/promptsCreate prompt + initial version
GET/api/prompts/{id_or_slug}Detail (with full version history)
PATCH/api/prompts/{id}Update name / description / active_version_id
DELETE/api/prompts/{id}Hard delete
POST/api/prompts/{id}/versionsAdd a version (no promote)
POST/api/prompts/{id}/versions/{vid}/activatePromote a version
POST/api/prompts/{id}/renderSubstitute variables, return text

All routes require a logged-in user; cross-user access returns 404.

Using rendered prompts in API requests

For now, the integration with chat-completions is manual: call the /render endpoint to get the substituted text, then paste it into your messages[0].content when calling /v1/chat/completions:

import httpx, os

JWT = os.environ["PR_JWT"]
API = "https://app.privaterouter.com"

# 1. Render the prompt
rendered = httpx.post(
    f"{API}/api/prompts/summarise-email/render",
    headers={"Authorization": f"Bearer {JWT}"},
    json={"variables": {"sender": "alice@example.com", "body": "..."}},
).json()["rendered_content"]

# 2. Send to chat-completions
resp = httpx.post(
    f"{API}/v1/chat/completions",
    headers={"Authorization": f"Bearer sk-..."},  # your API key
    json={
        "model": "privaterouter/fast",
        "messages": [{"role": "user", "content": rendered}],
    },
)

Auto-using a saved prompt slug inside /v1/chat/completions directly (e.g. {"prompt_slug": "summarise-email", "prompt_variables": {...}}) is a planned follow-up — for the M18 release the render endpoint is the integration point.

Tips

  • Use descriptive variable names. {{ customer_email }} is much easier to read in version diffs than {{ x }}.
  • Always set defaults for optional knobs (tone, length, formatting). It keeps callers simple and lets you change behaviour without breaking integrations.
  • Treat versions like git commits. Use the notes field to record what changed — future you will appreciate it during a rollback.