JWT Skill Management
Use this guide when a signed-in creator, operator, or OpenAnalyst package integration needs to manage a handle's hosted 10x skills through the control plane.
JWT skill management is for interactive or user-session-backed control-plane updates. It is separate from PAT-backed automation and from local OpenAnalyst plugin installation.
Auth and access
All routes in this guide use the 10x control-plane JWT from the signed-in user session:
Authorization: Bearer <JWT_TOKEN>
Requirements:
- The user must have
CREATORaccess or higher on the target handle. - Write routes require the tenant plan feature gate
growth_tools. - Use the 10x control-plane JWT. Do not use a Smartwork runtime token, provider/billing JWT, or PAT for these hosted skill writes unless a future endpoint explicitly documents PAT write scopes.
Routes
| Action | Route | Auth | Notes |
|---|---|---|---|
| List handle skills | GET /v2/handles/{handle}/skills | JWT, CREATOR+ | Returns draft, ready, published, and disabled hosted skills for one handle |
| Create or update skill metadata | PUT /v2/handles/{handle}/skills/{skillKey} | JWT, CREATOR+, growth_tools | Stores title, description, prompt, tools, and draft/disabled status |
| Configure runtime | PUT /v2/handles/{handle}/skills/{skillKey}/runtime | JWT, CREATOR+, growth_tools | Attaches a verified runtime image and execution limits |
| Publish skill | POST /v2/handles/{handle}/skills/{skillKey}/publish | JWT, CREATOR+, growth_tools | Requires an existing skill and runtime registry row |
| Delete skill | DELETE /v2/handles/{handle}/skills/{skillKey} | JWT, CREATOR+ | Removes the hosted skill record |
Skill keys are normalized to lowercase and must use the platform skill-key format. Keep them stable because downstream function bindings, MCP exposure, and OpenAnalyst imports may reference them.
Setup variables
export API_BASE="https://ai.10x.in"
export HANDLE="links"
export SKILL_KEY="crm-lookup"
export JWT_TOKEN="<10x-control-plane-jwt>"
List hosted skills
curl -sS "${API_BASE}/v2/handles/${HANDLE}/skills" \
-H "Authorization: Bearer ${JWT_TOKEN}"
The response is shaped for hosted catalog and OpenAnalyst import flows:
{
"skills": [
{
"skillKey": "crm-lookup",
"title": "CRM Lookup",
"description": "Look up CRM records.",
"systemPrompt": "Use this skill when CRM context is needed.",
"tools": [
{
"type": "http",
"name": "lookup",
"config": {
"url": "https://api.example.com/lookup"
}
}
],
"runtimeStatus": "ready",
"updatedAt": "2026-05-08T10:30:00.000Z"
}
]
}
runtimeStatus is computed:
draftwhen the skill is stored but no runtime is configured.readywhen the skill is draft and a runtime row exists.publishedafterPOST /publish.disabledwhen the stored skill status is disabled.
Create or update skill metadata
curl -sS -X PUT "${API_BASE}/v2/handles/${HANDLE}/skills/${SKILL_KEY}" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"displayName": "CRM Lookup",
"description": "Look up CRM records.",
"systemPrompt": "Use this skill when CRM context is needed.",
"tools": [
{
"type": "http",
"name": "lookup",
"config": {
"url": "https://api.example.com/lookup"
}
}
],
"status": "DRAFT"
}'
Response:
{
"ok": true,
"skillKey": "crm-lookup",
"status": "DRAFT"
}
PUT only accepts user-settable metadata status:
- Use
DRAFTfor editable hosted skills. - Use
DISABLEDto hide the skill from import and invocation surfaces. - Do not send
PUBLISHED; publishing is only set byPOST /publish.
Configure the runtime
curl -sS -X PUT "${API_BASE}/v2/handles/${HANDLE}/skills/${SKILL_KEY}/runtime" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"imageRef": "catalog/node:20",
"command": ["node", "server.js"],
"args": [],
"cpu": 512,
"memory": 1024,
"idleTimeoutSec": 900,
"maxSessionSec": 3600,
"secretsRefs": [],
"networkPolicy": {},
"planGates": {}
}'
The runtime image must be a verified catalog image (catalog/*) or a private ECR image. Other image references are rejected.
Response:
{
"ok": true,
"runtime": {
"handle": "links",
"skillKey": "crm-lookup",
"imageRef": "catalog/node:20",
"command": ["node", "server.js"],
"args": [],
"cpu": 512,
"memory": 1024,
"idleTimeoutSec": 900,
"maxSessionSec": 3600
}
}
Publish the skill
curl -sS -X POST "${API_BASE}/v2/handles/${HANDLE}/skills/${SKILL_KEY}/publish" \
-H "Authorization: Bearer ${JWT_TOKEN}"
Response:
{
"ok": true,
"skillKey": "crm-lookup",
"version": "v1770000000000"
}
Publishing requires both the skill metadata row and runtime configuration to exist. After publish, GET /skills returns runtimeStatus: "published" and a newer updatedAt.
Delete a hosted skill
curl -sS -X DELETE "${API_BASE}/v2/handles/${HANDLE}/skills/${SKILL_KEY}" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-i
A successful delete returns 204 No Content. Delete is idempotent for the hosted skill record, but any downstream docs, bindings, or local imports that referenced the skill should be reviewed separately.
OpenAnalyst package workflow
OpenAnalyst package should keep using the existing read/import/sync path for local skills:
- Use the 10x control-plane token to call
GET /v2/handles/{handle}/skills. - Import hosted skills into the local OpenAnalyst skill catalog.
- When hosted editing is needed, use the same 10x token for
PUT,PUT /runtime,POST /publish, and optionalDELETE. - After a hosted write, refetch
GET /skills. - Let the existing OpenAnalyst
updatedAtcomparison mark the local skill as updateable. - Install or update the local skill through the existing overwrite path.
Do not use the Smartwork runtime token or billing/provider JWT for these writes. Those tokens belong to different trust boundaries.
Common errors
| Status | Error | Meaning |
|---|---|---|
401 | invalid_token, token_expired | The JWT is missing, invalid, or expired |
403 | insufficient_role or access failure | Signed-in user does not have CREATOR+ access to the handle |
403 | feature_locked | The tenant does not have the growth_tools feature gate required for writes |
400 | invalid_skill_key | The skillKey path segment is not valid |
400 | invalid_tools, invalid_tool_type, invalid_tool_name | The tools array is malformed |
400 | invalid_image_ref | Runtime image is not a verified catalog or ECR image |
400 | runtime_not_configured | Publish was requested before runtime configuration exists |
404 | skill_not_found | Runtime or publish was requested for a missing hosted skill |
Related guides
Updated Jun 19, 2026
