Skills and canvas API
Create and govern skills, install packs, generate graphs, and run canvas previews through the REST API.
Use these session-authenticated endpoints to manage skills and their lifecycle, inspect role-derived permissions, install skill packs, generate graphs, and preview a graph without creating a persisted execution. Paths are relative to your Agent Studio origin and JSON requests use Content-Type: application/json.
X-Organization-Id. The caller must be an organization member, and creation requires skills:create. See API authentication, errors, and limits for shared failure behavior.Skill model
The create and update operations accept these fields. inputSchema, outputSchema, and each example input and output must be JSON objects no larger than 1 MB.
| Field | Type | Requirement and limits |
|---|---|---|
name | string | Required on create; 2–100 characters. |
purpose | string | Required on create; 5–1,000 characters. |
allowedTools | string[] | Required on create; 1–20 nonempty tool names. |
instructions | string | Optional; 5–20,000 characters when provided. |
inputSchema, outputSchema | object | Optional JSON objects. |
examples | object[] | Optional; at most 50. Each has input, output, and an optional description of at most 300 characters. |
actionsRequiringApproval | string[] | Optional; at most 20 nonempty values. |
approvalPolicy | object | Optional booleans alwaysRequireApproval and neverRequireApproval, plus toolBasedApproval and skillBasedApproval arrays of at most 20 values each. |
maxExecutionSteps | integer | Optional; 1–100. |
graphDefinition | object | Optional version-1 agent graph. |
notes | string | Optional; at most 5,000 characters. |
List skills
/api/skillsOrganization context; takes precedence over the query value.
Search text, at most 100 characters.
`DRAFT`, `PUBLISHED`, or `ARCHIVED`.
`updatedAt`, `name`, or `createdAt`.
`asc` or `desc`.
Select an organization instead of personal skills.
curl --request GET \ --url https://studio.example.com/api/skills?search=Support%20triage&status=PUBLISHED&sortBy=updatedAt&sortOrder=desc&organizationId=org_example \ --header 'X-Organization-Id: org_example'
{ "success": true, "data": [{ "id": "skill_example", "name": "Support triage" }] }Create a skill
/api/skillsOptional organization context.
Skill name.
Reader-facing purpose.
One to 20 allowed tool names.
Execution instructions.
Input contract.
Output contract.
Up to 50 input/output examples.
Actions that pause for approval.
Approval-policy object.
Execution ceiling from 1 through 100.
Optional version-1 graph.
Internal notes, at most 5,000 characters.
Organization context when the header is absent.
curl --request POST \
--url https://studio.example.com/api/skills \
--header 'Content-Type: application/json' \
--data '{
"name": "<string>",
"purpose": "<string>",
"allowedTools": [],
"instructions": "<string>",
"inputSchema": {},
"outputSchema": {},
"examples": [],
"actionsRequiringApproval": [],
"approvalPolicy": {},
"maxExecutionSteps": 0,
"graphDefinition": {},
"notes": "<string>",
"organizationId": "<string>"
}'{ "success": true, "data": { "id": "skill_example", "name": "Support triage" } }curl -X POST 'https://studio.example.com/api/skills' \
-H 'Content-Type: application/json' \
-H 'X-Organization-Id: org_example' \
--data '{
"name": "Support triage",
"purpose": "Classify a support ticket and draft a response.",
"instructions": "Classify the issue, gather facts, and draft a concise response.",
"allowedTools": ["knowledge_search"],
"maxExecutionSteps": 12
}'Get, update, or delete a skill
/api/skills/:idSkill ID.
curl --request GET \ --url https://studio.example.com/api/skills/:id
{ "success": true, "data": { "id": "skill_example", "name": "Support triage" } }/api/skills/:idSkill ID.
Updated name.
Updated purpose.
Replacement tool list; cannot be empty.
Updated instructions.
Updated input contract.
Updated output contract.
Replacement examples.
Updated approval actions.
Updated approval policy.
Updated 1–100 step ceiling.
Updated graph.
Updated notes.
curl --request PATCH \
--url https://studio.example.com/api/skills/:id \
--header 'Content-Type: application/json' \
--data '{
"name": "Support triage v2",
"purpose": "Classify a support ticket and draft a response.",
"allowedTools": [
"knowledge_search"
],
"instructions": "Classify the issue, gather facts, and draft a concise response.",
"inputSchema": "\"type\":\"object\"",
"outputSchema": "\"type\":\"object\"",
"examples": [
{
"input": {
"ticketId": "TICKET-1042"
},
"output": {
"category": "billing"
}
}
],
"actionsRequiringApproval": [
"send_response"
],
"approvalPolicy": "\"alwaysRequireApproval\":false",
"maxExecutionSteps": 12,
"graphDefinition": "\"version\":1,\"nodes\":[],\"edges\":[]",
"notes": "Draft reviewed by the support team."
}'{ "success": true, "data": { "id": "skill_example", "name": "Support triage v2" } }/api/skills/:idSkill ID.
curl --request DELETE \ --url https://studio.example.com/api/skills/:id
{ "success": true }Duplicate and lifecycle operations
All four operations require a signed-in session and an accessible skill. Duplicate creates another skill; publish, archive, and restore return the resulting skill or version state.
/api/skills/:id/duplicateSource skill ID.
curl --request POST \ --url https://studio.example.com/api/skills/:id/duplicate
{ "success": true, "data": { "id": "skill_copy" } }/api/skills/:id/publishSkill ID.
Version to publish.
curl --request POST \
--url https://studio.example.com/api/skills/:id/publish \
--header 'Content-Type: application/json' \
--data '{
"versionId": "version_example"
}'{ "success": true, "data": { "id": "version_example", "status": "PUBLISHED" } }/api/skills/:id/archiveSkill ID.
curl --request POST \ --url https://studio.example.com/api/skills/:id/archive
{ "success": true, "data": { "status": "ARCHIVED" } }/api/skills/:id/restoreSkill ID.
curl --request POST \ --url https://studio.example.com/api/skills/:id/restore
{ "success": true, "data": { "status": "DRAFT" } }See Publish, archive, and restore skill versions for the lifecycle model.
Skill permissions
Permissions are available only for organization skills. GET derives each member's skill permissions from the organization role: owners and admins receive all four permissions, members receive editor, executor, and viewer, and other roles receive viewer.
/api/skills/:id/permissionsOrganization skill ID.
curl --request GET \ --url https://studio.example.com/api/skills/:id/permissions
{ "success": true, "data": [{ "userId": "user_example", "orgRole": "MEMBER", "skillPermissions": ["SKILL_EDITOR", "SKILL_EXECUTOR", "SKILL_VIEWER"] }] }/api/skills/:id/permissionsOrganization skill ID.
Organization member ID.
Any of `SKILL_ADMIN`, `SKILL_EDITOR`, `SKILL_EXECUTOR`, and `SKILL_VIEWER`.
curl --request POST \
--url https://studio.example.com/api/skills/:id/permissions \
--header 'Content-Type: application/json' \
--data '{
"targetUserId": "user_example",
"permissions": [
"SKILL_VIEWER"
]
}'{ "success": true, "data": { "targetUserId": "user_example", "permissions": ["SKILL_VIEWER"] } }POST permission changes but does not establish durable per-skill permission persistence. Effective access remains role-derived. Do not treat the response as proof that an override is enforced./api/skills/:id/permissionsOrganization skill ID.
Member whose overrides should be reset.
curl --request DELETE \ --url https://studio.example.com/api/skills/:id/permissions?targetUserId=user_example
{ "success": true, "data": { "message": "Permissions reset to organization defaults" } }Skill packs
/api/skills/packscurl --request GET \ --url https://studio.example.com/api/skills/packs
{ "success": true, "data": [{ "id": "support-starter", "name": "Support starter" }] }/api/skills/packsPack catalog ID.
Server indexes to install; defaults to every server in the pack.
Skill indexes to install; defaults to every skill in the pack.
curl --request POST \
--url https://studio.example.com/api/skills/packs \
--header 'Content-Type: application/json' \
--data '{
"packId": "support-starter",
"serverIndices": [
0
],
"skillIndices": [
0
]
}'{ "success": true, "data": { "packId": "support-starter" } }Synthesize a skill
/api/skills/synthesizeNonempty workflow goal.
When `true`, mount matched servers and try to create the skill. Default: `false`.
Configured model identifier.
curl --request POST \
--url https://studio.example.com/api/skills/synthesize \
--header 'Content-Type: application/json' \
--data '{
"prompt": "Create a workflow that researches documentation updates.",
"autoMount": false,
"model": "model_example"
}'{ "success": true, "data": { "skillName": "Documentation researcher", "graph": { "version": 1, "nodes": [], "edges": [] }, "servers": [], "mountedServerIds": [], "skillId": null } }With autoMount: false, the result is advisory and skillId is null. With autoMount: true, graph generation can still succeed when skill creation fails; check the nullable skillId and optional warning rather than relying only on the HTTP status.
Sandbox skills
/api/skills/sandboxTemporary skill name.
Temporary skill purpose.
Workflow steps; cannot be empty.
MCP servers required by the steps.
Execution input.
curl --request POST \
--url https://studio.example.com/api/skills/sandbox \
--header 'Content-Type: application/json' \
--data '{
"skillName": "Support triage sandbox",
"skillDescription": "Classify a support ticket.",
"steps": [
{
"name": "classify",
"description": "Classify the ticket"
}
],
"requiredServers": [],
"inputData": "\"ticketId\":\"TICKET-1042\""
}'{ "success": true, "skillId": "skill_sandbox", "executionId": "exec_example" }/api/skills/sandboxSandbox skill ID.
curl --request DELETE \ --url https://studio.example.com/api/skills/sandbox?skillId=skill_sandbox
{ "success": true }Canvas generation
/api/canvas/copilotNonempty graph request.
Configured model identifier.
curl --request POST \
--url https://studio.example.com/api/canvas/copilot \
--header 'Content-Type: application/json' \
--data '{
"prompt": "Create a graph that classifies a support ticket.",
"model": "model_example"
}'{ "success": true, "graph": { "version": 1, "nodes": [], "edges": [] } }/api/canvas/optimize-promptCurrent prompt text.
Node type; defaults to `agent` for context.
Node label.
Router condition.
Tool name.
curl --request POST \
--url https://studio.example.com/api/canvas/optimize-prompt \
--header 'Content-Type: application/json' \
--data '{
"prompt": "Classify the ticket and return a category.",
"nodeType": "agent",
"label": "Classify ticket",
"condition": "priority == high",
"toolName": "knowledge_search"
}'{ "success": true, "optimizedPrompt": "Classify the ticket...", "stats": { "originalTokens": 20, "optimizedTokens": 28, "improvement": "+40%" } }Preview a graph
Register the preview before opening its stream. The first stream subscriber starts the dry run. The stream emits named server-sent events, sends a comment heartbeat every 15 seconds, and closes after a terminal execution status: COMPLETED, FAILED, CANCELLED, STEP_LIMIT_EXCEEDED, or PAUSED_FOR_APPROVAL.
/api/canvas/previewAccessible skill version ID.
Complete graph with START, END, and valid connections.
Input validated against the skill version; default: `{}`.
curl --request POST \
--url https://studio.example.com/api/canvas/preview \
--header 'Content-Type: application/json' \
--data '{
"skillVersionId": "version_example",
"graph": "\"version\":1,\"nodes\":[],\"edges\":[]",
"inputData": "\"ticketId\":\"TICKET-1042\""
}'{ "previewId": "preview-00000000-0000-4000-8000-000000000000" }/api/canvas/preview/:id/streamPreview ID returned by registration.
curl --request GET \ --url https://studio.example.com/api/canvas/preview/:id/stream
event: execution:status
data: {"type":"execution:status","executionId":"preview-example","status":"COMPLETED"}Preview storage is temporary and process-local. Events are not replayed for a later subscriber, so connect to the stream immediately and reconnect only by registering a new preview.