Studio APIs
Base URL and reference
POST {STUDIO_URL}/api/rpc/<procedure.path>A live, interactive reference is served by every deployment and generated from the same schemas the server validates against:
{STUDIO_URL}/api/rpc/api-reference
{STUDIO_URL}/api/rpc/api-reference/spec.jsonTypeScript client
The API is built with oRPC, so a fully typed client is available. Procedure names, inputs and outputs are checked at compile time.
import { createORPCClient } from "@orpc/client";
const agent = await client.agents.create({ name: "Support Triage" });
await client.agents.update({
agentId: agent.id,
goal: "Classify support email and draft a first reply.",
instructions: "Always reply in the customer's language...",
modelId: "openrouter/anthropic/claude-sonnet-4",
outputFormat: "TEXT",
});
await client.agents.deploy({ agentId: agent.id });Authentication
| Caller | Credential | Notes |
|---|---|---|
| Browser | Session cookie | Set at sign in |
| Server to server | API key, prefixed studio_ | Scoped to one organization |
Every organization scoped call re checks live membership against the database, so a stale active organization cannot grant access to a former member.
What every route does before your handler runs
- Authenticate the caller.
- Confirm live membership of the active organization.
- Record an audit observation.
- Enforce the authorization gate, by role, by object permission, or both.
Because the audit observer wraps the gate, a refusal is recorded as a Deny row with a reason. An unauthorized caller sending a malformed body gets a 403 with an audit row, not a 400 with nothing.
The modules
The router exposes agents, audit, connectors, organizations,
knowledgeBases, skills and toolkits (plus the healthCheck and
privateData probes). Note the plural names.
agents
| Group | Procedures |
|---|---|
| Lifecycle | create get list update delete deploy pause resume archive restore |
| Discovery | modelCatalog |
| Sessions | sessions.create sessions.get sessions.list sessions.rename sessions.delete |
| Runs | runs.create runs.stream runs.cancel runs.respond runs.list runs.get |
| Attachments | skills.attach skills.detach knowledgeBases.attach knowledgeBases.detach |
| Wiring | memory.set guardrail.set toolkits.list toolkits.accounts.set toolkits.mode.set |
| Sharing | sharing.share sharing.unshare sharing.list sharing.searchCandidates |
| Traces | traces.runs.list traces.runs.get traces.runs.export traces.steps.list traces.steps.get |
knowledgeBases
create get list update delete, plus:
folders.*for folder managementdocuments.initiateUpload/initiateUploadsandcompleteUpload/completeUploadsfor uploadsdocuments.getdocuments.listdocuments.deletedocuments.retrysearchsharing.*
skills
create get list update delete and sharing.*.
connectors
Model, memory and guardrail providers: list the catalogue, create, update, disconnect, reconnect and delete connections, and manage the model allowlist.
toolkits
list get accounts.connect accounts.setTools accounts.disconnect.
organizations
get list delete, member management, invitations, groups, roles and
permission editing, userPermissions, availablePermissions, logo upload.
audit
list export health.
Conventions that apply everywhere
| Convention | Detail |
|---|---|
| IDs | UUID v7 everywhere. Time ordered, so they also sort chronologically. |
| Pagination | Cursor based. Pass the last id as cursor. limit is 1 to 100, default 50. |
| Deletes | Soft. The object disappears from reads but history is preserved. |
| Request size | Bodies are capped at 10 MB. |
| Idempotency | Starting a run takes a requestIdempotencyKey. Repeating the same key returns the same run instead of starting a second one. |
| Secrets | Write only. API keys, OAuth tokens and vault contents are never returned. |
| Correlation | Send X-Request-Id and it is stamped on every audit row for that request. |
Errors
| Status | Meaning |
|---|---|
400 | Input failed validation |
401 | Not signed in |
403 | Signed in, but not permitted. An audit row is written. |
404 | Not found, or found but not reachable by you. Deliberately identical, so ids cannot be probed. |
409 | Conflict, usually two concurrent writes. Safe to retry. |
429 | Rate limited |
500 | Server error |
A complete example
Create, ground, publish and run an agent:
// 1. Create
const agent = await client.agents.create({ name: "Invoice Parser" });
// 2. Configure
await client.agents.update({
agentId: agent.id,
goal: "Extract structured data from an invoice PDF.",
instructions: "Return only fields present in the document. Never guess a value.",
modelId: "openai/gpt-4o-mini",
outputFormat: "JSON",
outputFormatConfig: {
schema: {
type: "object",
properties: {
supplier: { type: "string" },
invoiceNumber: { type: "string" },
total: { type: "number" },
},
required: ["supplier", "invoiceNumber", "total"],
},
},
});
// 3. Ground
await client.agents.knowledgeBases.attach({
agentId: agent.id,
knowledgeBaseId: kb.id,
});
// 4. Publish
await client.agents.deploy({ agentId: agent.id });
// 5. Run
const session = await client.agents.sessions.create({ agentId: agent.id });
const run = await client.agents.runs.create({
agentId: agent.id,
sessionId: session.id,
requestIdempotencyKey: crypto.randomUUID(),
input: { version: 1, parts: [{ type: "text", text: "Parse the attached invoice." }] },
});
// 6. Stream
for await (const event of client.agents.runs.stream({
agentId: agent.id,
sessionId: session.id,
runId: run.agentRunId,
})) {
if (event.type === "text-delta") process.stdout.write(event.delta);
}