/* eslint-disable */ const { useState: dS, useMemo: dM, useEffect: dE, useRef: dR } = React; // Code highlighter — minimal, matches existing .code .kw .str .com const codeify = (src, lang) => { let s = src; // escape s = s.replace(/&/g,"&").replace(//g,">"); if (lang === "bash" || lang === "sh") { s = s.replace(/(^|\n)(\s*)(#[^\n]*)/g, '$1$2$3'); s = s.replace(/("[^"]*")/g, '$1'); s = s.replace(/\b(curl|export|cat|echo|npm|npx|pnpm|yarn)\b/g, '$1'); s = s.replace(/(--?[a-zA-Z][\w-]*)/g, '$1'); } else if (lang === "json") { s = s.replace(/("(?:\\.|[^"\\])*")(\s*:)/g, '$1$2'); s = s.replace(/:(\s*)("(?:\\.|[^"\\])*")/g, ':$1$2'); s = s.replace(/\b(true|false|null)\b/g, '$1'); } else if (lang === "ts" || lang === "js") { s = s.replace(/(\/\/[^\n]*)/g, '$1'); s = s.replace(/("[^"]*"|'[^']*'|`[^`]*`)/g, '$1'); s = s.replace(/\b(import|from|const|let|var|function|return|async|await|new|class|export|default|if|else|for|of|in|interface|type)\b/g, '$1'); } else if (lang === "py") { s = s.replace(/(#[^\n]*)/g, '$1'); s = s.replace(/("[^"]*"|'[^']*')/g, '$1'); s = s.replace(/\b(import|from|def|return|class|if|else|for|in|async|await|with|as)\b/g, '$1'); } else if (lang === "http") { s = s.replace(/^(GET|POST|PUT|PATCH|DELETE)(\s)/gm, '$1$2'); s = s.replace(/^([A-Z][\w-]*):/gm, '$1:'); } return s; }; const Code = ({ lang="bash", children }) => { const flatten = (n) => { if (n == null || n === false) return ""; if (typeof n === "string" || typeof n === "number") return String(n); if (Array.isArray(n)) return n.map(flatten).join(""); if (n.props && n.props.children != null) return flatten(n.props.children); return ""; }; const txt = flatten(children); return
;
};

const DOCS_NAV = [
  { sec: "Get started" },
  { id:"intro", label:"Introduction" },
  { id:"quickstart", label:"Quickstart" },
  { id:"auth", label:"Authentication" },
  { sec: "API" },
  { id:"personas", label:"Personas" },
  { id:"chat", label:"Chat runtime" },
  { id:"openai", label:"OpenAI-compatible", pill:"compat" },
  { id:"streaming", label:"Streaming (SSE)" },
  { id:"webhooks", label:"Webhooks" },
  { sec: "Integrations" },
  { id:"mcp", label:"MCP server", pill:"new" },
  { id:"sdks", label:"SDKs" },
  { id:"cli", label:"CLI" },
  { sec: "Reference" },
  { id:"errors", label:"Errors" },
  { id:"limits", label:"Rate limits" },
  { id:"changelog", label:"Changelog" },
];

const DocsBlock = ({ id, eyebrow, title, sub, children }) => (
  
{eyebrow &&
{eyebrow}
}

{title}

{sub &&
{sub}
} {children}
); const KV3 = ({rows}) => ( {rows.map((r,i)=>( ))}
FieldTypeDescription
{r[0]} {r[1]} {r[2]}
); const Endpoint = ({ method, path, scope }) => (
{method} {path} {scope && scope · {scope}}
); const Callout = ({ kind="note", title, children }) => { const cls = kind==="warn" ? "warn" : kind==="bad" ? "bad" : ""; return (
{title || (kind==="warn"?"Caution":kind==="bad"?"Important":"Note")}
{children}
); }; const PageDocs = () => { const [active, setActive] = dS("intro"); const [lang, setLang] = dS("curl"); const scrollRef = dR(null); // Track active section on scroll dE(()=>{ const onScroll = () => { const ids = DOCS_NAV.filter(n=>!n.sec).map(n=>n.id); let cur = ids[0]; for (const id of ids) { const el = document.getElementById(id); if (!el) continue; const r = el.getBoundingClientRect(); if (r.top < 120) cur = id; } setActive(cur); }; window.addEventListener("scroll", onScroll, {passive:true}); return () => window.removeEventListener("scroll", onScroll); }, []); const jump = (id) => { const el = document.getElementById(id); if (!el) return; const y = el.getBoundingClientRect().top + window.scrollY - 70; window.scrollTo({top:y, behavior:"smooth"}); }; // Sample code snippets per language const snippetsCreate = { curl: `curl https://api.persona.dev/v1/personas \\ -H "Authorization: Bearer $PERSONA_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "intent": "35-year-old, lonely, intellectual ex-academic bookseller in Istanbul", "language": "tr", "visibility": "workspace" }'`, ts: `import Persona from "@persona/sdk"; const client = new Persona({ apiKey: process.env.PERSONA_API_KEY }); const persona = await client.personas.create({ intent: "35-year-old, lonely, intellectual ex-academic bookseller", language: "tr", visibility: "workspace", }); console.log(persona.id, persona.surface.name);`, py: `from persona import Persona client = Persona(api_key=os.environ["PERSONA_API_KEY"]) persona = client.personas.create( intent="35-year-old, lonely, intellectual ex-academic bookseller", language="tr", visibility="workspace", ) print(persona.id, persona.surface.name)`, }; const langPills = (
{[["curl","cURL"],["ts","TypeScript"],["py","Python"]].map(([k,l])=>( setLang(k)}>{l} ))}
); return (
{/* Left sub-nav */} {/* Main content */}
Persona — Developer reference

Build with personas. Bring your own model, runtime, and tools.

REST + SSE API, OpenAI-compatible chat endpoint, MCP server, and SDKs for TypeScript, Python, and Go. All endpoints scoped to your workspace.
Get an API key View on GitHub OpenAPI spec ↓ api.persona.dev/v1 all systems operational
Built around three primitives
  • Personas — generated, edited, forked, versioned.
  • Runs — every generation is a tracked, replayable run.
  • Conversations — multi-turn chat against any persona, with drift detection.
You can call it three ways
  • Native REST + SSE — api.persona.dev/v1
  • OpenAI-compatible — drop in as a model provider.
  • MCP server — stream personas as tools to any MCP host.
All requests require a workspace-scoped API key. Personas, runs, and conversations are tenant-isolated; nothing crosses workspaces unless you publish to the marketplace.
{langPills} {snippetsCreate[lang]}
Response · 201 Created
{`{ "id": "psn_3kP9aLwX2v", "slug": "the-istanbul-bookseller", "status": "completed", "language": "tr", "schema_version": "1.0", "soul": { "core_desire": "...", "core_fear": "...", "wound": "..." }, "self": { "big_five": { "O": 0.82, "C": 0.61, "E": 0.22, "A": 0.55, "N": 0.74 }, "archetype": "the-sage" }, "mask": { "voice": { "register": "wry, slightly archaic" }, "public_bio": "..." }, "surface": { "name": "Mert Kavaklı", "age": 35, "occupation": "Sahaf, eski akademisyen" }, "fingerprint": "sha256:9c4a...", "run": { "id": "run_a8K2zB", "cost_usd": 0.79, "tokens": 18421, "latency_ms": 24310 } }`}
Header
{`Authorization: Bearer pk_live_abcd1234efgh5678 Persona-Workspace: ws_acme_studio Idempotency-Key: 8f3c2e1a-... # optional`}
Available scopes
{["personas:read","personas:write","personas:fork","runs:read","runs:cancel","chat:invoke","chat:read","webhooks:manage","config:read","config:write","keys:manage"].map(s=>( {s} ))}
Default key: personas:* runs:read chat:invoke
Keys with pk_live_ prefix are unmasked once at creation and never again. Rotate within 90 days. Live keys cannot be used against test workspaces, and vice versa.

Create — request body

Free-form character brief (1–4000 chars). Required.], ["language", "ISO-639-1", <>Persona's primary speaking language. Default en.], ["visibility", "enum", <>One of private, workspace, public. Default private.], ["pipeline", "string", <>Override the active pipeline (Team+). e.g. strict-audit.], ["seed", "integer", <>Reproducibility seed. Pinned across the run.], ["budget_usd", "number", <>Hard cost cap. Run aborts with 402 if exceeded.], ["custom_fields","object", <>Workspace-defined fields registered via POST /v1/schema/fields.], ]}/>

Persona object — top-level keys

{`curl https://api.persona.dev/v1/chat/conversations/conv_72/messages \\ -H "Authorization: Bearer $PERSONA_API_KEY" \\ -H "Content-Type: application/json" \\ -d '{ "persona_id": "psn_3kP9aLwX2v", "message": "Sahafına yeni gelen bir kitabı anlatır mısın?", "tools": ["web.search"], "stream": true }'`}
Response — assistant turn
{`{ "message_id": "msg_91xQ", "role": "assistant", "content": "Bu sabah elime, 1973 baskısı bir Yaşar Kemal geçti. Kapağı...", "drift": { "voice": 0.08, "values": 0.04, "verdict": "in-character" }, "coherence": 4.6, "tools_used": [], "tokens": { "in": 1240, "out": 312 }, "cost_usd": 0.014 }`}
Base URL
{`POST /v1/openai/chat/completions POST /v1/openai/embeddings GET /v1/openai/models`}
Host: api.persona.dev
Model naming
{`persona/{persona_id} persona/{persona_slug} persona/{persona_slug}@{version}`}
e.g. persona/the-istanbul-bookseller@v3
{`import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.persona.dev/v1/openai", apiKey: process.env.PERSONA_API_KEY, }); const completion = await client.chat.completions.create({ model: "persona/the-istanbul-bookseller", messages: [ { role: "user", content: "Sevdiğin bir şair var mı?" }, ], temperature: 0.8, }); console.log(completion.choices[0].message.content);`}

Compatibility matrix

FeatureStatusNotes
chat.completionsfullIncluding tools, streaming, JSON mode.
embeddingsfullVoyage v3 by default; configurable.
moderationpartialPersona-aware classifier; categories differ slightly.
audio.*roadmapVoice cloning for Mask layer is planned for Q3.
fine_tuning.*n/aUse Persona refine + version history instead.
system messagesoverlayMerged on top of the persona's runtime prompt.
Pass X-Persona-Conversation-Id to attach a completion to an existing conversation — drift and lineage will be tracked. Without it, each request is a one-shot.
{`curl -N https://api.persona.dev/v1/personas?stream=true \\ -H "Authorization: Bearer $PERSONA_API_KEY" \\ -H "Accept: text/event-stream" \\ -d '{"intent": "..."}'`}
{`event: run.started data: {"run_id":"run_a8K","step":"intent_parse"} event: step.completed data: {"step":"soul_draft","tokens":2104,"cost_usd":0.08} event: layer.delta data: {"path":"soul.core_desire","value":"To be seen ..."} event: run.completed data: {"persona_id":"psn_3kP9aLwX2v","total_cost_usd":0.79}`}

Event types

Verifying signatures

{`import crypto from "node:crypto"; export function verify(req, secret) { const sig = req.headers["persona-signature"]; // "t=...,v1=..." const [t, v1] = sig.split(",").map(p => p.split("=")[1]); const payload = t + "." + req.rawBody; const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) { throw new Error("invalid signature"); } if (Date.now() / 1000 - Number(t) > 300) throw new Error("stale"); }`}

Subscribable events

{["persona.created","persona.updated","persona.forked","persona.archived","run.completed","run.failed","conversation.drift_detected","moderation.flagged","audit.test_failed","webhook.test"].map(e=>( {e} ))}
Retries: 5 attempts over ~1h with jitter. After exhaustion, deliveries land in the dead-letter queue and can be replayed for 30 days.
Server URL
{`https://mcp.persona.dev/sse Authorization: Bearer $PERSONA_API_KEY Persona-Workspace: ws_acme_studio`}
SSE transport · stdio bridge available via the CLI.
Exposed tools
  • persona.invoke — chat against a persona
  • persona.search — semantic + keyword search
  • persona.fork — fork from a parent
  • ensemble.scenario — multi-persona scene

Claude Desktop config

{`{ "mcpServers": { "persona": { "command": "npx", "args": ["-y", "@persona/mcp", "--workspace", "ws_acme_studio"], "env": { "PERSONA_API_KEY": "pk_live_..." } } } }`}

Tool: persona.invoke

MCP clients only see personas your API key can read. Personas with visibility=private are excluded unless the key owner created them.
{[ ["TypeScript", "@persona/sdk", "npm i @persona/sdk", "v1.0.4", "moss"], ["Python", "persona-sdk", "pip install persona-sdk", "v1.0.2", "moss"], ["Go", "persona-go", "go get persona.dev/sdk", "v0.9.0", "gold"], ["Ruby", "persona-ruby", "gem install persona-sdk", "v0.6.1", "gold"], ["Rust", "persona-rs", "cargo add persona", "v0.4.0", "gold"], ["Elixir", "persona_ex", "{:persona, \"~> 0.3\"}", "v0.3.0", ""], ].map(([name, pkg, install, ver, tone])=>(
{name}
{ver}
{pkg}
{install}
))}
{`# install npm i -g @persona/cli # log in (opens browser) persona auth login # bridge MCP over stdio for Claude Desktop / Cursor persona mcp serve --workspace ws_acme_studio # create a persona from a markdown brief persona personas create -f ./brief.md --visibility workspace # tail run events persona runs tail run_a8K2zB # pull a persona to a local YAML file (round-trips with edit + push) persona personas pull psn_3kP9aLwX2v -o bookseller.yml`} {`{ "error": { "code": "persona.cost_budget_exceeded", "message": "Run aborted: $1.20 exceeds budget $1.00.", "type": "billing", "doc_url": "https://docs.persona.dev/errors#cost_budget_exceeded", "request_id": "req_8f3c2e1a9b", "run_id": "run_a8K2zB" } }`}

Status reference

StatusCodeWhen
400request.invalidMalformed body, unknown field, schema mismatch.
401auth.invalid_keyMissing or revoked key.
402persona.cost_budget_exceededRun hit hard budget cap.
403moderation.intent_blockedIntent blocked by moderation policy.
409persona.slug_takenSlug collision in workspace.
422persona.schema_violationCross-layer consistency check failed.
429rate_limit.exceededSlow down — see Retry-After.
503provider.unavailableUpstream model provider failure (fallback exhausted).
PlanGenerations / minChat msgs / minConcurrent runsWebhook deliveries / min
Free530160
Pro6060010600
Team3003 000503 000
Enterprisecustomcustomcustomcustom
Headers on every response
{`X-RateLimit-Limit: 60 X-RateLimit-Remaining: 41 X-RateLimit-Reset: 1746790012 Retry-After: 7`}
{[ ["2026-05-09", "v1.0.4", "moss", "MCP server stable. New tool: ensemble.scenario."], ["2026-04-22", "v1.0.3", "", "OpenAI compat: tools + JSON mode reach parity."], ["2026-04-08", "v1.0.2", "", "SSE: layer.delta now emits JSON-patch (was full snapshots)."], ["2026-03-25", "v1.0.1", "", "Webhooks: signed timestamp + 5min replay window."], ["2026-03-11", "v1.0.0", "ember","General availability. SLA 99.9%, regional residency."], ].map(([d,v,tone,note], i)=>(
{d}
{v}
{note}
))}
{/* Right rail — on-this-page (decorative anchor) */}
); }; window.PageDocs = PageDocs;