ClouisleClouisle

Skills API

Import, inspect, update, delete, and test sandbox skills

A skill is a sandboxed capability package an Agent can call: it is installed as a package from a ZIP archive or a Git repository, is owned either by a team (team_id set) or by the platform (team_id is null, i.e. a system skill), and it executes inside the code sandbox. Base path: /api/v1/skills.

Prerequisites and authentication

All endpoints require an authenticated JWT user session (Authorization: Bearer <token>); API-key authentication is not accepted.

Permission codes are the feature-level switch; team ownership is enforced separately: GET list/detail, PATCH, DELETE, and POST /test all require the caller to be a member of the owning team, and update/delete/test additionally require OWNER/ADMIN of that team. For system skills (team_id=null), update/delete/test require a superuser.

Skills can only be installed through the import flow: there is no create-from-scratch endpoint and no /files-style skill file management endpoint.

Endpoints

MethodPathPurposePermission
GET/api/v1/skillsList the skills available to a team (system + team, unpaginated)skill:read
POST/api/v1/skills/import/preview-zipUpload a ZIP, scan it, and start an import sessionskill:create
POST/api/v1/skills/import/preview-gitClone a Git repository, scan it, and start an import sessionskill:create
POST/api/v1/skills/import/{session_id}/installInstall/update the packages selected in a sessionskill:create
GET/api/v1/skills/{skill_id}Get skill details, including package contentsskill:read
PATCH/api/v1/skills/{skill_id}Update skill metadata and default configskill:update
DELETE/api/v1/skills/{skill_id}Delete a skill and its private storageskill:delete
POST/api/v1/skills/{skill_id}/testExecute a skill once in the sandbox with test argumentsskill:execute

Authentication and permissions

PermissionPurpose
skill:readList skills, read skill details
skill:createPreview imports (ZIP / Git) and install imports
skill:updateUpdate skills
skill:deleteDelete skills
skill:executeTest-execute skills

Team access goes through check_team_access (which checks the team:read permission): a non-superuser must be a member of the target team and hold the global team:read permission; with require_admin=True (update, delete, test) they must also be an OWNER/ADMIN of it.

Skill object

FieldTypeDescription
idstring (UUID)Skill ID
team_idstring (UUID) | nullOwning team; null means a system skill
namestringStable name, max 100 characters, unique per team (team + name)
display_namestringDisplay name, max 100 characters
descriptionstringDescription
iconstring | nullIcon emoji or URL
categorystringCategory (enum below)
versionstringVersion, default 1.0.0
source_typestringInstall source: zip, git, manual_text, legacy
source_uristring | nullRedacted source URI (Git/imported packages)
source_refstring | nullGit ref or resolved revision
source_subdirstring | nullSource subdirectory scanned during import
package_pathstring | nullPackage root path inside the source
package_hashstring | nullContent hash of the installed package
input_schemaobjectJSON Schema of the arguments exposed to the model
default_configobjectDefaults merged into an agent's per-tool config
is_enabledbooleanWhether the skill can be selected/executed, default true
is_systembooleantrue when team_id is null
import_warningsarray of stringNon-blocking import warnings
created_by_idstring (UUID) | nullCreator ID
created_by_namestring | nullCreator username
created_at / updated_atstring (ISO 8601)Creation and update timestamps

Skill detail object

The detail endpoints add the package contents to the fields above:

FieldTypeDescription
skill_mdstringRaw SKILL.md content
instructionsstringInstructions parsed from SKILL.md
frontmatterobjectSKILL.md frontmatter
package_manifestobjectPackage manifest summary
execution_configobjectValidated execution configuration
config_schemaobjectJSON Schema of the skill configuration

Enums

category: file, code, data, web, api, other (default other).


List skills

GET /api/v1/skills

Returns every skill available to the given team, split into a system array and a team array. Not paginated.

Query parameters

ParameterTypeRequiredDefaultDescription
team_idstring (UUID)Yes-Target team; the caller must be a member (superusers excepted)
include_systembooleanNotrueAlso return system skills (team_id=null)
enabledbooleanNo-When set, only return skills whose is_enabled matches
searchstringNo-Case-insensitive contains match on name, display_name, description
categorystringNo-Filter by skill category
curl -X GET "https://your-domain.com/api/v1/skills?team_id=b7f2c9d4-1a3e-4f5b-8c6d-9e0a1b2c3d4e&include_system=true&enabled=true&search=analysis&category=data" \
  -H "Authorization: Bearer YOUR_TOKEN"

200 OK:

{
  "code": 0,
  "data": {
    "system": [
      {
        "id": "5c2f9a10-8b3d-4e6f-9a71-b2c3d4e5f607",
        "team_id": null,
        "name": "file_read",
        "display_name": "File Reader",
        "description": "Reads files from the workspace",
        "icon": "📄",
        "category": "file",
        "version": "1.0.0",
        "source_type": "zip",
        "source_uri": "system-skills.zip",
        "source_ref": null,
        "source_subdir": null,
        "package_path": "/data/skills/system/file_read",
        "package_hash": "sha256:1f0c0b7d2a9e4c8f",
        "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}},
        "default_config": {},
        "is_enabled": true,
        "is_system": true,
        "import_warnings": [],
        "created_by_id": null,
        "created_by_name": null,
        "created_at": "2026-01-10T09:00:00Z",
        "updated_at": "2026-01-10T09:00:00Z"
      }
    ],
    "team": [
      {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "team_id": "b7f2c9d4-1a3e-4f5b-8c6d-9e0a1b2c3d4e",
        "name": "data_analysis_skill",
        "display_name": "Data Analysis Skill",
        "description": "Analyzes tabular CSV datasets and generates statistical charts",
        "icon": null,
        "category": "data",
        "version": "1.0.0",
        "source_type": "git",
        "source_uri": "https://github.com/example/skills.git",
        "source_ref": "9c1f4a7b2d8e3f5061a2b3c4d5e6f70819a2b3c4",
        "source_subdir": "data_analysis",
        "package_path": "/data/skills/team/data_analysis_skill",
        "package_hash": "sha256:8d2e6f1a4b0c9d3e",
        "input_schema": {"type": "object", "properties": {"dataset": {"type": "string"}}},
        "default_config": {"max_rows": 1000},
        "is_enabled": true,
        "is_system": false,
        "import_warnings": [],
        "created_by_id": "3d1f7a92-5b6c-4f8e-9a01-2c3d4e5f6a7b",
        "created_by_name": "alice",
        "created_at": "2026-03-01T12:00:00Z",
        "updated_at": "2026-03-01T12:00:00Z"
      }
    ]
  },
  "msg": "success"
}

Omitting team_id returns 422 + 1001 (FastAPI validation failure), not an empty list. The envelope is data.system / data.team with no total / page fields, and it does not use an {items: []} shape.

Errors:

HTTPCodeMeaning
4221001Required team_id missing
4044004team_not_found
4033000Missing skill:read permission
4033002not_team_member

Import flow

Installing a skill is a two-step process: preview (create a short-lived import session) then install (choose packages from the session). Import sessions live for 1 hour; after that, install returns 400 + 1002 (skill_import_session_expired). The install request only needs package paths — team_id and the source come from the session.

Preview a ZIP import

POST /api/v1/skills/import/preview-zip

Upload a ZIP as multipart/form-data; the server scans it for skill packages.

Form fieldTypeRequiredDescription
filefileYesZIP archive (.zip); other extensions are rejected
team_idstring (UUID)NoTeam that will own the imported skills; the caller must be an OWNER/ADMIN of it. Omit to import system skills, which is superuser-only

Limits: archive at most 50 MB, at most 500 files, expanded size at most 50 MB, and any single file at most 10 MB.

curl -X POST "https://your-domain.com/api/v1/skills/import/preview-zip" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "team_id=b7f2c9d4-1a3e-4f5b-8c6d-9e0a1b2c3d4e" \
  -F "file=@skills.zip"

Preview a Git import

POST /api/v1/skills/import/preview-git
FieldTypeRequiredDescription
team_idstring (UUID) | nullNoSame as the ZIP preview
repo_urlstringYesRepository URL, 1–2000 characters; validated before cloning
refstring | nullNoBranch / tag / commit, max 255 characters; defaults to the repository default branch

The clone times out after 180 seconds; a timeout or an invalid URL returns 400 + 1002 (skill_git_url_invalid).

curl -X POST "https://your-domain.com/api/v1/skills/import/preview-git" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"team_id":"b7f2c9d4-1a3e-4f5b-8c6d-9e0a1b2c3d4e","repo_url":"https://github.com/example/skills.git","ref":"main"}'

Preview response

Both previews return the same shape:

{
  "code": 0,
  "data": {
    "session_id": "f0e1d2c3-b4a5-4968-8778-99aabbccddee",
    "source_type": "zip",
    "source_uri": "skills.zip",
    "source_ref": null,
    "source_subdir": null,
    "skills": [
      {
        "package_path": "data_analysis_skill",
        "name": "data_analysis_skill",
        "display_name": "Data Analysis Skill",
        "description": "Analyzes tabular CSV datasets",
        "version": "1.0.0",
        "category": "data",
        "icon": null,
        "valid": true,
        "errors": [],
        "warnings": [],
        "conflict": null,
        "file_count": 4,
        "package_hash": "sha256:8d2e6f1a4b0c9d3e"
      }
    ],
    "invalid": [],
    "warnings": []
  },
  "msg": "success"
}

Preview item fields

FieldTypeDescription
package_pathstringPackage path relative to the archive/repository root
name / display_namestring | nullSkill name and display name
descriptionstringDescription
versionstringVersion, default 1.0.0
categorystringCategory
iconstring | nullIcon
validbooleanfalse means the package cannot be installed
errorsarray of stringi18n message keys explaining why, e.g. skill_md_not_found
warningsarray of stringMessage keys such as skill_name_conflict, skill_duplicate_name_in_source
conflictobject | null{"type": "existing_team_skill", "skill_id": ..., "message": ...} when a same-named skill already exists in the target team
file_countintegerFiles in the package
package_hashstring | nullPackage content hash

Packages with valid: false appear in invalid instead of skills; errors / warnings entries are i18n message keys that the client must translate.

Install an import

POST /api/v1/skills/import/{session_id}/install

Path parameters

ParameterTypeDescription
session_idstring (UUID)The session ID returned by a preview route

Request body

FieldTypeRequiredDefaultDescription
itemsarrayNo[]Packages to install; empty installs nothing
items[].package_pathstringYes-Must match a package_path in the preview
items[].actionstringNoinstallinstall, update, or skip
items[].skill_idstring | null (UUID)No-Explicit target for update; must belong to the session's team
is_enabledbooleanNotrueis_enabled applied to the installed/updated skills

Package selection rules:

  • install: fails for that package (skill_name_exists) when the target team already has a skill with the same name.
  • update: uses the explicit skill_id first; otherwise matches a team skill by name; if neither exists the whole request fails with 404 + 4000 (skill_not_found).
  • skip: records the package_path without touching any skill.
{
  "items": [
    {"package_path": "data_analysis_skill", "action": "install"},
    {"package_path": "charts_skill", "action": "update", "skill_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"}
  ],
  "is_enabled": true
}

200 OK:

{
  "code": 0,
  "data": {
    "installed": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"],
    "updated": [],
    "skipped": ["charts_skill"],
    "errors": ["other_skill: skill_name_exists"]
  },
  "msg": "Skills imported successfully"
}
FieldTypeDescription
installedarray of UUIDIDs of newly created skills
updatedarray of UUIDIDs of updated skills
skippedarray of stringSkipped package_path values
errorsarray of stringPer-package errors as "<package_path>: <message_key>"

Per-package failures do not change the HTTP status: even with a non-empty errors, the response is 200 OK + code: 0. Clients must inspect data.errors; judging success solely by HTTP status misses failures.


Get skill

GET /api/v1/skills/{skill_id}

Query parameters

ParameterTypeRequiredDescription
team_idstring (UUID)NoTeam context. A team skill always requires membership of its owning team; for a system skill, passing team_id requires membership of that team, while omitting it performs no team check

200 OK: data is the skill detail object.

Errors:

HTTPCodeMeaning
4044000skill_not_found
4044004team_not_found
4033000Missing skill:read permission
4033002not_team_member

Update skill

PATCH /api/v1/skills/{skill_id}

Only the fields present in the body are applied.

Request body

FieldTypeDescription
display_namestring | nullDisplay name, 1–100 characters
descriptionstring | nullDescription
iconstring | nullIcon, max 100 characters
categorystring | nullCategory
is_enabledboolean | nullEnable or disable the skill
default_configobject | nullDefault configuration

200 OK: data is the updated skill detail object.

Errors:

HTTPCodeMeaning
4044000skill_not_found
4033000Missing skill:update permission, or a system skill without a superuser
4033002 / 3003Not a team member / not a team OWNER-ADMIN

Delete skill

DELETE /api/v1/skills/{skill_id}

Deletes the skill row and its private package storage. A skill still referenced by an agent's tools_config cannot be deleted.

200 OK: data: null.

Errors:

HTTPCodeMeaning
4044000skill_not_found
4001002skill_referenced_by_agent: still referenced by an agent
4033000Missing skill:delete permission, or a system skill without a superuser
4033002 / 3003Not a team member / not a team OWNER-ADMIN

Test skill

POST /api/v1/skills/{skill_id}/test

Executes the skill once in the code sandbox. Test privileges match update: superuser for system skills, team OWNER/ADMIN for team skills.

Request body

FieldTypeRequiredDefaultDescription
argumentsobjectNo{}Arguments passed to the skill
configobjectNo{}Configuration overrides for this run

200 OK:

{
  "code": 0,
  "data": {
    "success": true,
    "result": {"rows": 1200, "columns": 8},
    "error": null,
    "stdout": "processed sales.csv\n",
    "stderr": "",
    "artifacts": [
      {
        "path": "/workspace/report.html",
        "optional": false,
        "description": "Generated report",
        "file_type": "file",
        "size": 20480,
        "checksum": "sha256:...",
        "content_type": "text/html",
        "storage_path": "skills/artifacts/report.html",
        "url": null,
        "filename": "report.html"
      }
    ],
    "duration_ms": 842
  },
  "msg": "success"
}
FieldTypeDescription
successbooleanWhether the run succeeded
resultanySkill return value
errorstring | nullFailure reason
stdout / stderrstringCaptured standard output / error
artifactsarrayRun artifacts (see the sandbox artifact shape in the File Uploads API)
duration_msinteger | nullRun duration in milliseconds

A failed skill run does not return a non-2xx status: the response is still 200 OK + code: 0, with the failure in data.success: false and data.error. Judging success solely by HTTP status treats an execution failure as success.

Errors:

HTTPCodeMeaning
4044000skill_not_found
4033000Missing skill:execute permission, or a system skill without a superuser
4033002 / 3003Not a team member / not a team OWNER-ADMIN

Error handling

HTTPCodeTrigger
4012000 / 2001 / 2002Missing, invalid, or expired token
4221001Request validation failed: missing team_id, unknown category, display_name length out of range, malformed UUID
4001002Invalid ZIP/Git source, archive over limits, expired session, skill_referenced_by_agent
4033000Missing permission code, or a system skill without a superuser (skill_system_admin_required)
4033002 / 3003not_team_member / team_admin_required
4044000 / 4004Skill not found / team not found
4001003Internal server error

Per-package errors from preview and install appear only in the response's errors field and are never expressed through the HTTP status.

  • Skills — installing, enabling, and using skills in Agents
  • Agents API — referencing a skill from an agent's tools_config
  • File Uploads API — sandbox artifacts and file parsing
  • Error Handling — recover by HTTP status and business error code

How is this guide?

On this page