New Sign up free, 10 calls on us. Up to $1, no card needed.

Prompt Library

Keep prompts in your workspace instead of in your code: versioned, labelled, and called by id from any OpenAI SDK. Moving a label changes what your code gets on its next request, with no redeploy.

Calling a prompt

Use the ordinary /v1/chat/completions, /v1/responses or /v1/messages endpoint and add prompt_id and prompt_variables to the body. The SDKs require model and messages: send any model name and an empty user message. In the OpenAI and Anthropic SDKs the extra fields go in extra_body.

import os
from openai import OpenAI

client = OpenAI(base_url="https://synthorai.io/v1", api_key=os.environ["SYNTHORAI_API_KEY"])

reply = client.chat.completions.create(
    model="any",  # the version's model is used
    messages=[{"role": "user", "content": ""}],  # required by the SDK; dropped
    extra_body={
        "prompt_id": "support-reply",
        "prompt_variables": {
            "product": "Acme Cloud",
            "customer_message": "My invoice shows two charges.",
        },
    },
)
print(reply.choices[0].message.content)

Over plain HTTP the fields sit at the top level of the body. This call asks for the version the staging label points to:

curl https://synthorai.io/v1/chat/completions \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "any",
    "messages": [],
    "prompt_id": "support-reply",
    "prompt_label": "staging",
    "prompt_variables": {"product": "Acme Cloud", "customer_message": "My invoice shows two charges."}
  }'

Fields

Field Type What it does
prompt_id string The prompt’s id in your workspace, as shown on the Prompts page.
prompt_variables object Values for the version’s {{name}} variables: strings, numbers or booleans, inserted as text. A variable you leave out takes the version’s default; with no default the call fails.
prompt_version integer Pins one version. Without it, the call gets the version the production label points to.
prompt_label string Calls the version another label points to, such as staging. Send a version or a label, not both.

What the request becomes

  • The version’s messages come first, then yours. Your own messages are how a conversation continues: earlier turns, tool calls and tool results are kept as you send them. Only an empty user message, the one the SDKs make you send, is dropped.
  • The version’s model replaces the one in the request. A version without a model keeps yours.
  • Your parameters win. The version’s temperature, max_tokens, top_p and JSON mode apply only where your request does not set the same parameter.
  • The four prompt_* fields stop at the gateway. The provider receives an ordinary request.
  • On /v1/responses the messages go into input (a string input is your user turn), max_tokens becomes max_output_tokens, and JSON mode becomes text.format.

On /v1/messages

The same four fields work with the Anthropic SDK. The version’s system text goes in front of your own system, its messages in front of yours, and temperature, top_p and max_tokens apply where your request does not set them. JSON mode has no field on this endpoint and is left out.

import os
from anthropic import Anthropic

client = Anthropic(base_url="https://synthorai.io", api_key=os.environ["SYNTHORAI_API_KEY"])

reply = client.messages.create(
    model="any",  # the version's model is used
    max_tokens=1024,
    messages=[{"role": "user", "content": ""}],  # required by the SDK; dropped
    extra_body={
        "prompt_id": "support-reply",
        "prompt_variables": {
            "product": "Acme Cloud",
            "customer_message": "My invoice shows two charges.",
        },
    },
)
print(reply.content[0].text)

Tool calls and multi-turn chat

Send tools and the conversation so far exactly as you would without a library prompt. The version supplies the system prompt and the opening message; your messages carry the rest of the conversation, including the model’s tool calls and your tool results.

# Round 2 of a tool loop: send the conversation so far, as without a prompt.
# The gateway puts the version's messages first and these after them.
reply = client.chat.completions.create(
    model="any",
    tools=tools,
    messages=[
        {"role": "user", "content": ""},  # the SDK placeholder, dropped
        first.choices[0].message,           # the model's tool call
        {"role": "tool", "tool_call_id": call.id, "content": lookup_invoice(call)},
    ],
    extra_body={"prompt_id": "support-reply", "prompt_variables": variables},
)

Tools and output schema in a version

A version can hold function tools, a tool choice and a JSON schema for the answer; they are written in the shape of the endpoint you call. Tools your request sends are added after the version’s, and a tool of yours with the same name replaces the version’s. Your own tool_choice and response format win.

Partials

A partial is a block of text kept once and included in prompts with {{>id}}. When you save a prompt version, each include is pinned to the partial’s newest version, shown as {{>id@4}}, so a saved version does not change when the partial does. The prompt’s page shows when a newer partial version exists and offers to update, which saves a new version of the prompt. A partial’s variables are filled from the call’s prompt_variables. A partial cannot be called on its own, and one that saved versions include cannot be deleted.

Versions and labels

Each save creates the next version, and a saved version never changes. production, staging and development exist on every prompt; you can add your own labels. Point production at a version to ship it, and back at the old one to roll back. Your code keeps calling the same id.

A label move or a delete reaches calls within about 10 seconds. Moving labels and deleting prompts need an admin or the workspace owner.

Trying a draft

The editor on the Prompts page has the draft on the left and a place to try it on the right. Give each variable a value and press Run: the draft is rendered and sent to its model, and the answer comes back with its latency and cost. The call is billed and logged like a Playground call, and nothing of the draft is kept until you save the version. Show the request shows the rendered request for each endpoint without calling a model.

Testing a version before you ship it

A prompt’s Tests hold test sets: rows of values for its variables, with an optional user message and expected answer, and the checks an answer must pass (contains, a pattern, equals the expected answer, valid JSON, follows the output schema, a limit on latency or cost, or a model that judges against a criterion you write). A run calls the version you pick and the version you compare it with for every row, shows the two answers side by side, and reports how many rows passed, the cost and the latency of each. Each call is billed and logged like a Playground call, under one trace per run. When you move a label, the dialog shows the version’s last run; it informs the move and does not block it. Test sets and the answers of a run are stored encrypted; answers are kept 30 days.

Gradual rollout

A label can send a share of its calls to another version: “production: v6, v7 at 10%”. Which version a call gets is decided by its session id, or its end user when it has no session, or else per request, so a conversation stays on one version. X-Synthorai-Prompt-Version says which one answered, and the Prompts page shows the two side by side. An admin changes the share, moves the label to the candidate, or stops the rollout; a call that pins prompt_version is never part of it.

Change webhooks

Admins can add up to three addresses on the Prompts page that receive a POST when a prompt is created, gets a version, has a label moved or a rollout changed, or is deleted. Each request is signed: X-Synthorai-Signature: t=…,v1=…, the HMAC-SHA256 of the timestamp, a dot and the raw body, with the secret shown once when the address is added. A version’s text is sent only if you switch that on for the address. A failed delivery is retried three more times over about half an hour; after five events in a row are given up, the address is switched off until you switch it on again.

POST /hooks/prompts
X-Synthorai-Event: prompt.label_moved
X-Synthorai-Signature: t=1791140000,v1=5f2c…

{
  "type": "prompt.label_moved",
  "created_at": "2026-10-05T03:12:44Z",
  "prompt": "support-reply",
  "kind": "prompt",
  "label": "production",
  "from": 6,
  "version": 7,
  "by": 42
}

# Verify: hex(HMAC-SHA256(secret, "1791140000" + "." + raw_body)) == v1

Importing, exporting and OpenAI’s stored prompts

New prompt › Import reads a pasted Chat Completions or Responses request body, or a LiteLLM .prompt file, into a first version: its messages, model, parameters, tools and output schema. A prompt’s Export downloads it with every version as one JSON file, which imports the same way, and Copy to a workspace copies it into another workspace you belong to. OpenAI’s stored prompts stop working on 2026-11-30 and cannot be read out: recreate the prompt here under its pmpt_… id, and a /v1/responses request that sends prompt with that id is served from the library with no code change. Its version, when it is a number, picks that library version; without one the call gets production.

Rendering without calling a model

POST /v1/prompts/{id}/render returns the request a call would send, and the version it resolved to, without calling a model. Send prompt_variables, optionally prompt_version or prompt_label, and format: chat (the default), responses or messages. It is not billed and does not appear in Logs; it counts toward the key’s rate limit. Use it to check what a version sends, or to call an endpoint the prompt_* fields do not reach.

curl https://synthorai.io/v1/prompts/support-reply/render \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "format": "chat",
    "prompt_variables": {"product": "Acme Cloud", "customer_message": "My invoice shows two charges."}
  }'

{
  "prompt_id": "support-reply",
  "version": 6,
  "format": "chat",
  "request": {
    "model": "claude-sonnet-5",
    "temperature": 0.3,
    "messages": [
      {"role": "system", "content": "You answer billing mail for Acme Cloud."},
      {"role": "user", "content": "My invoice shows two charges."}
    ]
  }
}

Response headers

X-Synthorai-Prompt-Version says which version answered, which a call by label cannot know otherwise. If the library cannot be read, the gateway answers from the last copy it read, for up to 24 hours, and adds X-Synthorai-Prompt-Stale: 1; a label moved in the meantime takes effect once the library is readable again.

Errors

These are returned before any model is called, so they are never billed. They use the error shape of the endpoint you called.

Status Code When
400 prompt_variable_missing A variable has no value and no default. The message names it.
400 prompt_invalid A field is malformed: both a version and a label, an object or a list as a variable value, more than 64 variables, or more than 64 KB of them. A rendered prompt over 1 MB is refused the same way. So is a call that leaves nothing to send: every message of the version renders empty and the request has none of its own.
404 prompt_not_found No prompt with this id in the key’s workspace, no such version, or the label points to no version yet.
409 prompt_unreadable The stored version can no longer be read. Save a new version.
503 prompt_unavailable The library could not be read just now and the gateway holds no recent copy of this prompt. Retry.

Statistics per version

Every call is recorded with the prompt and the version it ran. The Prompts page shows calls, errors, spend, tokens, latency and time to first token per version, over the last 7, 30 or 90 days; a version’s call count opens those calls in Logs. Run a new version under staging next to production and compare them before you move production.

In Logs, filter with prompt = support-reply and prompt_version = 7; a request’s detail names its prompt and version. Exported records carry them too: run.prompt_id and run.prompt_version in files and webhooks, gen_ai.prompt.name and gen_ai.prompt.version on OTLP spans.

See also: Prompts · Chat Completions · Responses API