Skip to main content
This guide answers a question the API reference doesn’t: not what each endpoint does, but where in your own stack you’re supposed to call it from. It assumes you’re integrating your product (a “Project” in Persona terms — Beyond Campus, Coursify, and OpenFounder are our own first three) with Persona’s agent infrastructure via /api/v1/developer/*. The short version: every endpoint in this API is a server-to-server call. Your backend calls Persona’s backend. Your browser/mobile app never does.

1. Your Project credential is a server-side secret

When you mint a credential (POST /api/v1/projects/{projectId}/credentials, from your own Clerk-authenticated admin session — see the separate admin/control-plane docs), you get back a keyId and a secret. Every call to /api/v1/developer/* authenticates with
This is architecturally decided, not a suggestion: a Project credential must never be usable from a browser context. Treat it exactly like a Stripe secret key, not a publishable key — it lives in your backend’s environment (env var, secrets manager), and it never ships inside a frontend bundle, a mobile app binary, or a URL a browser can see. If your integration needs any of this API’s data or behavior visible to an end user, your own backend fetches it and relays it onward in whatever form your frontend already expects (a JSON response from your own API, a WebSocket message, etc.) — Persona is never called directly from code running in someone else’s browser.

2. The same endpoint serves two different purposes, and you choose which

Most Developer endpoints (Agents, Skills, Knowledge, MCP, Threads) don’t have separate “admin” and “end-user” URLs. The same route behaves differently depending on one thing: whether your request also carries
  • Omit the header → the call acts on behalf of your Project itself — use this for actions your own team or admin tooling initiates: provisioning an Agent when your product launches a new feature, wiring up a Knowledge base, configuring a Provider. This is “control-plane” usage.
  • Include the header → the call acts on behalf of one of your own end users — use this when your backend is handling a request that a specific logged-in person of yours triggered: Sabik opening a chat, Rahul uploading a document to a Knowledge base. This is “runtime-plane” usage.
Critically: your backend decides which case applies and sets the header itself, after your own auth system has already confirmed who’s making the request. Never take a raw user id out of a browser request and forward it verbatim — Persona trusts whatever identity your credential asserts, so that assertion has to come from a step your backend actually verified.

3. AG-UI chat/streaming is a server-to-server call too

POST /api/v1/developer/agui streams an agent’s response as Server-Sent Events. If you want live, streaming chat UX in your own product’s frontend, the pattern is: your backend opens the SSE connection to Persona, reads the stream, and relays it onward to your own browser client over whatever channel you already use to talk to your frontend (your own SSE endpoint, a WebSocket, a GraphQL subscription — your choice), authenticated however you already authenticate your users. A browser never connects to /api/v1/developer/agui directly — doing so would require shipping your Project credential to that browser, which point 1 above rules out.

4. Per-resource quick reference

5. Where the SDK fits

A Node.js/TypeScript SDK is planned as an ergonomic wrapper around exactly the rules above — typed resource clients plus a streaming chat client. It changes nothing about where code runs: the SDK itself is a server-side package, meant to be installed in your backend’s node_modules, never bundled into a frontend build. Its own README states this directly rather than assuming it’s obvious.