Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

API reference

All endpoints are mounted at /v1/modules/scaiskills/ and authenticate with the standard ScaiGrid bearer token. Responses use ScaiGrid's standard envelope ({ "data": ... } for success, { "error": ... } for failures). Permission checks happen per-endpoint against the keys documented in Permissions.

Skills#

POST /skills#

Register a new skill identity. Requires scaiskills:publish.

Field Required Notes
slug yes Lowercase letters, digits, hyphens. Pattern: ^[a-z][a-z0-9-]{2,63}$. Globally unique.
visibility no private (default) or public.
description no Free text, up to 500 chars.
bash
1
2
3
4
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug": "summarise", "visibility": "private"}'

Returns { "data": { "id", "slug", "owner_workspace_id", "visibility", "description", "created_at" } }.

GET /skills#

List skills visible to the caller's workspace — own skills plus all public skills across the deployment. Requires scaiskills:view.

GET /skills/{slug}#

Fetch one skill's metadata and full version list. Requires scaiskills:view. Private skills owned by another workspace return SCAISKILLS_SKILL_NOT_FOUND, not 403.

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "data": {
    "id": "skill_abc",
    "slug": "summarise",
    "owner_workspace_id": "ten_xyz",
    "visibility": "private",
    "description": "...",
    "created_at": "2026-05-17T10:00:00Z",
    "versions": [
      {
        "id": "ver_001",
        "semver": "0.1.0",
        "status": "published",
        "content_hash": "sha256:...",
        "published_at": "..."
      }
    ]
  }
}

DELETE /skills/{slug}#

Hard-delete the skill, every version, every binding, and every grant. Requires scaiskills:manage. Workspace must own the skill. Returns { "deleted": true, "slug": "..." }.

Versions#

POST /skills/{slug}/versions#

Publish a new version. Requires scaiskills:publish and ownership of the skill. Multipart upload with one field, bundle, containing the .tar.gz.

bash
1
2
3
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills/summarise/versions" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -F "bundle=@summarise-0.1.0.tar.gz"

The validator checks:

  • Bundle is a valid gzip-compressed tar.
  • SKILL.md is present at the root with valid YAML frontmatter.
  • Manifest name equals the URL slug.
  • Manifest version is strictly greater than every previously published semver for this skill.
  • References paths don't escape the bundle root.
  • Secrets entries match the v1.1 schema.

On success returns the new version row including content_hash, storage_uri, and status: "published".

On validation failure returns a non-standard error envelope with success: false and a context.errors array:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "success": false,
  "error": {
    "code": "SCAISKILLS_VALIDATION_FAILED",
    "message": "Bundle validation failed",
    "context": {
      "errors": [
        {"code": "MANIFEST_VERSION_NOT_MONOTONIC", "message": "...", "location": "SKILL.md:3"}
      ]
    }
  }
}

POST /skills/{slug}/versions/{semver}/yank#

Mark a version as yanked. Requires scaiskills:publish and ownership. Existing bindings pinned to the version keep working; new bindings against the exact semver fail with SCAISKILLS_YANKED_VERSION; floating refs (latest, ^x.y) skip yanked versions.

Returns { "semver": "0.2.0", "status": "yanked" }.

Bindings#

POST /bindings#

Create a binding with eager dependency resolution. Requires scaiskills:bind.

Field Required Notes
skill_id yes The skill's id (not slug).
version yes 0.1.0 (exact), latest, ^1.2, ~1.2, >=1.0. The leading @ is optional.
scope_type yes workspace, channel, user, or core.
scope_id yes Id under the scope type.
secret_mappings no { "secret_name": "vault_path" }. Required entries for any required: true secret in the manifest.

What happens server-side:

  1. Resolve the version ref against published, non-yanked versions.
  2. Walk manifest.requires.skills depth-first, resolve each dependency, detect cycles, build the lockfile.
  3. Check granted permissions vs. declared permissions, mapped secrets vs. required secrets, set pending_grants.
  4. Persist the binding row with resolved_deps_json and secret_mappings_json.
  5. Invalidate the Redis cache for the scope.

Returns the full binding row.

GET /bindings?scope_type=&scope_id=#

List bindings for a given scope. Requires scaiskills:view. Both query parameters are required.

DELETE /bindings/{binding_id}#

Delete a binding and cascade its grants. Requires scaiskills:bind. Invalidates the scope's Redis cache. Returns { "deleted": true } (or { "deleted": false } if the id wasn't found — idempotent).

POST /bindings/{binding_id}/permissions/grant#

Grant one declared permission on a binding. Requires scaiskills:grant.

json
1
2
3
{
  "permission": "scaidrive:read:/policies/"
}

After the grant, the binding's manifest is rechecked; if every declared permission is granted and every required secret is mapped, pending_grants flips to false. Scope cache is invalidated.

Returns { "id", "binding_id", "permission_string", "granted_at" }.

Resolve#

POST /resolve#

Return the lightweight manifest list for a scope. Requires scaiskills:view. Called by the runtime — ScaiWave per turn (scope_type: channel | user), ScaiCore at boot (scope_type: core).

json
1
2
3
4
5
6
{
  "scope_type": "channel",
  "workspace_id": "ten_xyz",
  "channel_id": "chn_support",
  "user_id": "usr_alice"
}

The ancillary workspace_id, channel_id, user_id, and core_id fields are merged into the candidate set; scope_type is the primary scope and dictates the Redis cache key.

Returns:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "data": {
    "skills": [
      {
        "slug": "summarise",
        "version": "1.0.0",
        "description": "...",
        "triggers": ["summarise", "tl;dr"]
      }
    ],
    "cache_ttl_ms": 60000
  }
}

Active, non-pending_grants bindings only. Deduplicated by skill_id with precedence core > user > channel > workspace. Cached in Redis for 60 seconds; every binding write for the scope invalidates.

MCP tools#

The progressive-disclosure surface the LLM calls during a turn. Each call logs a row in mod_scaiskills_invocations for metering.

POST /mcp/skills/list#

The lightweight manifest list for the caller's scope. Identical body shape to /resolve; identical output shape. Requires scaiskills:view.

POST /mcp/skills/search#

Semantic search over bound skills. Query parameter query plus the same scope body as /mcp/skills/list. Requires scaiskills:view.

bash
1
2
3
4
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/mcp/skills/search?query=refund" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope_type": "workspace", "workspace_id": "ten_xyz"}'

Two-stage pipeline: a ScaiMatrix vector search over the indexed manifests, intersected against the caller's resolved set so out-of-scope skills cannot leak. On ScaiMatrix failure, falls back to a substring filter over the resolved set. Each match includes score and match_excerpt.

POST /mcp/skills/view/{slug}#

Return the full SKILL.md body (default) or a file under references/. Requires scaiskills:view and an active binding for the slug in the caller's scope — otherwise SCAISKILLS_SKILL_NOT_FOUND.

Query parameter path selects an alternative file: references/refund-policy.md (or just refund-policy.md — the references/ prefix is added if missing). Absolute paths and .. segments are rejected.

Returns:

json
1
2
3
4
5
6
{
  "data": {
    "content": "When asked for a summary, follow these patterns...",
    "content_type": "text/markdown"
  }
}

The YAML frontmatter is stripped from SKILL.md so the LLM only sees the body.

Errors#

ScaiSkills-specific codes:

Code HTTP Meaning
SCAISKILLS_SKILL_NOT_FOUND 404 Slug doesn't exist or isn't visible to the caller.
SCAISKILLS_VERSION_NOT_FOUND 404 No version with this semver for the skill.
SCAISKILLS_BINDING_NOT_FOUND 404 Binding id doesn't exist.
SCAISKILLS_SLUG_CONFLICT 409 Slug already taken (globally).
SCAISKILLS_VERSION_CONFLICT 409 Version exists or is not strictly greater than the previous version.
SCAISKILLS_VALIDATION_FAILED 422 Bundle or manifest didn't validate. context.errors enumerates.
SCAISKILLS_DEPENDENCY_CYCLE 422 requires.skills formed a cycle during bind resolution.
SCAISKILLS_UNRESOLVABLE_DEPENDENCY 422 A dependency ref didn't match any published version.
SCAISKILLS_PENDING_GRANTS 409 Binding has ungranted permissions or unmapped required secrets when an active path was attempted.
SCAISKILLS_YANKED_VERSION 410 Tried to bind to a yanked version.
SCAISKILLS_BUNDLE_TOO_LARGE 413 Bundle exceeded the size cap.
SCAISKILLS_STORAGE_ERROR 500 S3 put/get failed.

All errors follow ScaiGrid's standard envelope with code, message, optional details, and a request id in meta.

Updated 2026-05-18 15:01:32 View source (.md) rev 12