Appearance
Endpoint: Prompt Management
Create, read, update, and delete prompts in VeriPrompt.
Authentication
Prompt list and create operations support three authentication methods:
1. Session Auth (Browser/Dashboard)
Standard NextAuth session cookies — used automatically by the VeriPrompt UI.
2. Gateway API Key (Recommended for programmatic access)
Use a FULL_ACCESS or OPENAI_COMPAT Gateway API key created in Admin > API Management.
bash
curl -X POST https://app.veriprompt.tech/api/v1/prompts \
-H "Authorization: Bearer vp-gw_sk_your-key-here" \
-H "Content-Type: application/json" \
-d '{"name": "My Prompt", "userPrompt": "Summarize: {{text}}"}'| Gateway Key Type | Create Prompts | List Prompts |
|---|---|---|
FULL_ACCESS | Yes | Yes |
OPENAI_COMPAT | Yes | Yes |
EXECUTE_ONLY | No | No |
READ_ONLY | No | No |
3. External API Key (Chains/Objects system)
For integration with the chains/objects system. Requires specific permission scopes in routingOverrides.permissions:
create:prompts— for creating promptsread:prompts— for listing all company promptsread:own-prompts— for listing only own prompts
Create a Prompt
POST /api/v1/prompts
Auth: Session, Gateway Key (FULL_ACCESS/OPENAI_COMPAT), or External API Key (create:prompts)Request Body
json
{
"name": "Customer Follow-up",
"userPrompt": "Write a follow-up email for {{customerName}} regarding {{topic}}.",
"systemPrompt": "You are a professional customer support agent.",
"description": "Generates follow-up emails for support tickets",
"temperature": 0.7,
"maxTokens": 500,
"category": "support",
"tags": ["email", "follow-up"],
"visibility": "PRIVATE"
}Required Fields
| Field | Type | Constraints |
|---|---|---|
name | string | 1-100 characters, must be unique within your company |
userPrompt | string | Minimum 1 character. Must be unique (content hash is checked). |
Optional Fields
| Field | Type | Default | Constraints |
|---|---|---|---|
systemPrompt | string | - | System instruction for the AI |
description | string | - | Human-readable description |
temperature | number | 0.7 | Range: 0.0 - 2.0 |
maxTokens | number | 1000 | Range: 1 - 8000 |
category | string | - | Free-text category label |
tags | string[] | [] | Array of tag strings |
visibility | enum | PRIVATE | PRIVATE, SHARED, or PUBLIC |
repositoryId | string | - | Assign to a specific repository |
branch | string | main | Repository branch |
Note: Each prompt's
userPromptcontent is hashed (SHA-256). Two prompts with identicaluserPrompttext cannot coexist. Add unique identifiers or variables to differentiate similar prompts.
Response (201 Created)
json
{
"success": true,
"prompt": {
"id": "clxyz123...",
"promptId": "prompt_1706000000_abc123",
"name": "Customer Follow-up",
"userPrompt": "Write a follow-up email for {{customerName}}...",
"temperature": 0.7,
"maxTokens": 500,
"visibility": "PRIVATE",
"isActive": true,
"createdAt": "2026-01-15T10:00:00Z"
}
}List Prompts
GET /api/v1/prompts
Auth: Session, Gateway Key (FULL_ACCESS/OPENAI_COMPAT), or External API Key (read:prompts/read:own-prompts)Which prompts are listed
The list only contains prompts the caller is allowed to see:
| Caller | Sees |
|---|---|
| Session user | Own prompts, PUBLIC prompts, SHARED prompts with no share list, and SHARED prompts shared with them or one of their teams. Account owners and admins of that company see every prompt in it. |
| Gateway API key | Exactly what the key's owner would see in a session. A key cannot list a colleague's PRIVATE prompt — the same rule applies when the key executes prompts (gateway, MCP). |
| External API key | read:prompts: all company prompts. read:own-prompts: only prompts created by that key. |
Admin access is per company. What counts is the role you hold in the company the prompts belong to — the role set under that company's user management — not your role in another company. If you are an admin in your own company and a regular member in a partner company, a key for the partner company sees only what a member sees there. An admin role in a company you were deactivated in grants nothing. This applies everywhere prompt visibility applies: the list, prompt execution, the gateway, MCP tools and chains, and test suites.
Example: Ana and Ben work in the same company. Ben has a PRIVATE prompt "Salary review". Ana's gateway key lists Ana's prompts and the company's PUBLIC/shared ones, but not "Salary review". If Ben shares it with Ana (or with a team Ana is in), it appears in her key's list too.
Query Parameters
| Param | Type | Description |
|---|---|---|
query | string | Search by name or description |
category | string | Filter by category |
tags | string | Comma-separated tag filter |
repositoryId | string | Filter by repository |
visibility | enum | PRIVATE, COMPANY, or PUBLIC |
limit | number | Results per page (1-100, default: 20) |
offset | number | Pagination offset (default: 0) |
sortBy | enum | createdAt, updatedAt, name, totalExecutions |
sortOrder | enum | asc or desc (default: desc) |
Response
json
{
"prompts": [ ... ],
"pagination": {
"total": 42,
"limit": 20,
"offset": 0,
"hasMore": true
}
}Get a Prompt
GET /api/v1/prompts/{id}
Auth: Session or Gateway KeyReturns the full prompt object including latest version content.
Update a Prompt
PUT /api/v1/prompts/{id}
Auth: SessionAll fields are optional - only include fields you want to change:
json
{
"name": "Updated Name",
"userPrompt": "Updated prompt text",
"temperature": 0.5,
"isActive": false
}Delete a Prompt
DELETE /api/v1/prompts/{id}
Auth: SessionNote: Active prompts must be deactivated before deletion. Set
isActive: falsevia the update endpoint first, then delete.
What deactivating does. A deactivated prompt no longer runs anywhere it is used by reference:
- MCP
execute_stored_promptand custom MCP tools answer as if the prompt did not exist. - An MCP chain tool with a deactivated step is refused and no longer listed in
tools/list. - A Studio chain with a deactivated step runs no step at all and reports "Stored prompt … is deactivated" for that step. Reactivate the prompt, or replace the step, to run the chain again.
- A test suite that targets a deactivated prompt can still be created and edited, but running it is refused (
409) until you reactivate the prompt. - A gateway request (
POST /api/gateway/execute) that names it inmetadata.promptIdis refused with409("This prompt is deactivated. Reactivate it to run it."). This includes the prompt's own Execute page and the Gateway mode of its Test panel, so reactivate a prompt before trying it out. The Test panel's API Agent mode sends the prompt's text rather than a reference to it, so it still runs, but as plain text without the prompt's own settings.
For example, if step 3 of a five-step Studio chain uses a deactivated prompt, steps 1 and 2 do not run either, so you are not charged for a run that cannot finish.
The same applies to a Studio chain step whose prompt is missing, not visible to you, or has no production version, when that step would stop the chain (stop on error is on and the step does not continue on error): the run is refused before any step runs, with that step's usual error. If the chain is set to continue past a failing step, the other steps still run and that step fails at its turn. A Studio chain run uses each prompt as it was when the run started: deactivating a prompt, or publishing a new production version, takes effect from the next run.
Clone a Prompt
POST /api/v1/prompts/clone
Auth: Sessionjson
{
"sourcePromptId": "clxyz123..."
}Creates a copy of the prompt with a new name and ID.
Version History
GET /api/v1/prompts/{id}/versions
Auth: Session or Gateway KeyReturns the version history for a prompt, ordered by creation date.
Execute a Stored Prompt
Use the MCP endpoint with a gateway key:
bash
curl -X POST https://app.veriprompt.tech/api/mcp \
-H "Authorization: Bearer sk-vp-your-key" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "execute_stored_prompt",
"arguments": {
"promptId": "prompt_123_abc",
"variables": {
"customerName": "Jane Smith",
"topic": "order #4567"
}
}
},
"id": 1
}'Test in Chat
Turn one version into protected Markdown files for a vendor chat (Claude, ChatGPT or Gemini), then bring the chat's answer back onto the version. These routes never call a model or the gateway. Hands-on guide: Test a Prompt in a Chat.
- Auth: session only (the browser UI); gateway and external API keys are not accepted.
- Who: people who can edit the prompt, in a company whose plan includes Shield data protection (
403 FEATURE_NOT_IN_PACKAGEotherwise; a suspended company gets403 COMPANY_SUSPENDED). - Caching: every answer carries
Cache-Control: no-store. - Errors are
{ "code": "…", "error": "…" }.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/prompts/{id}/chat-test?versionId=… | Can this version be tested? Test sets, vendors, naked rule |
POST | /api/v1/prompts/{id}/chat-test | Create one protected file per test set (up to 20) |
GET | /api/v1/prompts/{id}/chat-test/runs?versionId=… | Your own last 50 runs on this prompt |
POST | /api/v1/prompts/{id}/chat-test/import | Bring the returned answer back: restore, verdict, checks, encrypted response on the version |
Create the files
bash
curl -X POST https://app.veriprompt.tech/api/v1/prompts/cm9sup0rt0001/chat-test \
-H "Cookie: next-auth.session-token=…" \
-H "Content-Type: application/json" \
-d '{ "versionId": "cm9ver0002", "testSetIds": ["cm9ts0001"], "vendor": "claude", "naked": false }'The 201 answer lists one entry per file in runs (ref such as vp-run-k7qm2xw4ad, fileName, markdown, handoffId) and, per test set that failed, an entry in errors. The file holds stand-ins only and asks the assistant to answer as vp-run-<ref>.md.
Import the answer
bash
curl -X POST https://app.veriprompt.tech/api/v1/prompts/cm9sup0rt0001/chat-test/import \
-H "Cookie: next-auth.session-token=…" \
-H "Content-Type: application/json" \
-d '{
"fileName": "vp-run-k7qm2xw4ad.md",
"text": "VERIPROMPT CHECK: CLEAN\n\nDear Jon Doe-665gf1, we have refunded order 4711 …",
"rating": 4
}'| Field | Required | Rule |
|---|---|---|
text | yes | the returned file or the pasted answer, at most 2,000,000 characters |
fileName | no | a vp-run-<ref> in it picks the run |
runId | no | the run's ref, when neither fileName nor text carries one |
rating | no | 1 to 5 |
json
{
"run": { "ref": "vp-run-k7qm2xw4ad", "versionId": "cm9ver0002", "testSetId": "cm9ts0001", "vendor": "claude", "status": "IMPORTED" },
"verdict": { "status": "clean", "reason": null, "lineCount": 1, "firstLine": true },
"checks": {
"results": [
{ "kind": "contains", "expected": "Anna Schmidt", "passed": true },
{ "kind": "contains", "expected": "within 5 working days", "passed": false },
{ "kind": "pattern", "expected": "order\\s+4711", "passed": true }
],
"passed": 2,
"total": 3
},
"responseId": "cm9resp0007",
"restored": { "text": "Dear Anna Schmidt, we have refunded order 4711 …", "highlights": [], "replaced": 1, "expiredOrDeleted": 0, "unrecognised": 0 }
}- Only the person who created the run can import it: the restore puts back your stand-ins. Any other run answers
404 RUN_NOT_FOUNDwith the same sentence. - The restored answer is stored encrypted as a response of the run's version (company retention and storage limits apply) and appears on the prompt's responses and compare pages.
- A run is imported once. A second import answers
409 RUN_ALREADY_IMPORTED. A withheld answer is the exception: it comes back as201withimported: falseandresponseId: null, nothing is stored and the run stays open, so a corrected answer can be imported for the same run. - The restore counts toward your Shield quota like any handoff restore.
| Status | code | When |
|---|---|---|
| 400 | BAD_CHAT_IMPORT_REQUEST | text missing, or a field has the wrong shape |
| 404 | RUN_NOT_FOUND | no run of yours on this prompt matches |
| 409 | RUN_ALREADY_IMPORTED | the run was already imported |
| 409 | RESPONSE_STORAGE_DISABLED / STORAGE_LIMIT_REACHED | your company keeps no responses (0 days), or its response storage is full |
| 410 | HANDOFF_GONE | the run's handoff expired or was deleted, so the real values cannot come back |
| 413 | TOO_LARGE | text is over 2,000,000 characters |
| 422 | RUN_REF_MISSING | no vp-run- ref and no runId: pick the run from …/chat-test/runs |
| 429 | QUOTA_EXCEEDED | your Shield quota is used up |
Test set expectations
The checks live on the test set. GET /api/v1/prompts/{promptId}/test-sets and GET …/test-sets/{testSetId}/values return two more fields; POST …/test-sets and PUT …/test-sets/{testSetId}/values accept them (on PUT, values may be left out). Test values and expectations hold real data, so all four routes answer only people who can open the prompt in its own company (another company's prompt is 404 PROMPT_NOT_FOUND, for reads too) and carry Cache-Control: no-store:
| Field | Rule |
|---|---|
expectedContains | up to 20 phrases of 1 to 500 characters; matched ignoring case and extra whitespace |
expectedPattern | one regular expression of at most 300 characters, matched ignoring case. Backreferences, lookarounds, nested repetition such as (a+)+, (a?b?)+ or (a|b)* and counts above 1000 are refused with 400 BAD_EXPECTED_PATTERN |
bash
curl -X PUT https://app.veriprompt.tech/api/v1/prompts/support-reply/test-sets/cm9ts0001/values \
-H "Cookie: next-auth.session-token=…" \
-H "Content-Type: application/json" \
-d '{ "expectedContains": ["Anna Schmidt", "within 5 working days"], "expectedPattern": "order\\s+4711" }'